Initial commit: Wonderware / System Platform tools and reference

Five tools under one repo, all docs organized per DOCS-GUIDE.md:

- aalogcli: .NET 4.8 / x86 CliFx CLI for reading System Platform binary
  logs (*.aaLGX) for LLM debugging, built on aaOpenSource/aaLog. Commands:
  last, tail, range, unread, fields. Stable JSON envelope under --llm-json.
  Build template under lib/build/ for rebuilding aaLogReader.dll.

- aot: ArchestrA Object Toolkit 2014 v4.0 reference material. Dev guide
  (Markdown converted from CHM), API reference for the ArchestrA.Toolkit
  namespace, and the Monitor / Watchdog VS sample solutions.

- graccesscli: .NET 4.8 / x86 CliFx CLI that automates Galaxy
  configuration via the ArchestrA GRAccess COM interop. Includes session
  daemon, IPC protocol, and llm-json envelope contract.

- grdb: SQL/DDL exploration of the Galaxy Repository database. DDL
  captures, reusable queries, hierarchy / contained-name <-> tag-name
  translation notes.

- histdb: LLM-oriented reference for AVEVA Historian retrieval. INSQL
  linked-server, extension tables, every wwXxx time-domain extension,
  every retrieval mode, alarm/event SQL recipes, REST API. Distilled
  from the 243-page Historian Retrieval Guide.

Root contains:
- CLAUDE.md: thin index pointing into each tool's README.
- DOCS-GUIDE.md: doctrine for organizing docs for LLM consumption.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-03 18:22:20 -04:00
commit 32f26272ae
411 changed files with 69973 additions and 0 deletions
@@ -0,0 +1,819 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using ArchestrA.GRAccess;
using CliFx;
using CliFx.Attributes;
using CliFx.Infrastructure;
using ZB.MOM.WW.GRAccess.Cli.GRAccess;
using ZB.MOM.WW.GRAccess.Cli.Infrastructure;
namespace ZB.MOM.WW.GRAccess.Cli.Commands
{
public abstract class RoutedCommandBase : ICommand
{
[CommandOption("galaxy", 'g', Description = "Galaxy name", IsRequired = true)]
public string GalaxyName { get; init; }
[CommandOption("node", 'n', Description = "GR node name. Required for one-shot mode; ignored when a session is active.")]
public string NodeName { get; init; } = "";
[CommandOption("json", Description = "Output as JSON")]
public bool Json { get; init; }
[CommandOption("llm-json", Description = "Output stable machine JSON envelope")]
public bool LlmJson { get; init; }
public abstract string Command { get; }
public abstract string Subcommand { get; }
public virtual Dictionary<string, object> Args() => new Dictionary<string, object>
{
["json"] = Json,
["llm-json"] = LlmJson
};
public async ValueTask ExecuteAsync(IConsole console)
{
var args = Args();
await CommandRouter.ExecuteAsync(
console,
GalaxyName,
NodeName,
Command,
Subcommand,
args,
galaxy => GRAccessCommandDispatcher.Execute(galaxy, Command, Subcommand, args))
.ConfigureAwait(false);
}
}
public abstract class ConfirmedRoutedCommandBase : RoutedCommandBase
{
[CommandOption("confirm", Description = "Required for mutating commands")]
public bool Confirm { get; init; }
[CommandOption("confirm-target", Description = "Required exact target for mutating/destructive commands")]
public string ConfirmTarget { get; init; } = "";
[CommandOption("dry-run", Description = "Validate a mutating command without invoking mutating GRAccess calls")]
public bool DryRun { get; init; }
public override Dictionary<string, object> Args()
{
var args = base.Args();
args["confirm"] = Confirm;
args["confirm-target"] = ConfirmTarget;
args["dry-run"] = DryRun;
return args;
}
}
public abstract class ObjectCommandBase : ConfirmedRoutedCommandBase
{
[CommandOption("name", Description = "Object tagname", IsRequired = true)]
public string ObjectName { get; init; }
[CommandOption("type", 't', Description = "Object type: all, template, instance")]
public string Type { get; init; } = "all";
public override Dictionary<string, object> Args()
{
var args = base.Args();
args["name"] = ObjectName;
args["type"] = Type;
return args;
}
}
public abstract class BulkObjectsCommandBase : ConfirmedRoutedCommandBase
{
[CommandOption("name", Description = "Object tagname. May be repeated.")]
public IReadOnlyList<string> Names { get; init; } = Array.Empty<string>();
[CommandOption("pattern", 'p', Description = "GRAccess name pattern. May be repeated. Use % as wildcard.")]
public IReadOnlyList<string> Patterns { get; init; } = Array.Empty<string>();
[CommandOption("type", 't', Description = "Object type: all, template, instance")]
public string Type { get; init; } = "instance";
public override Dictionary<string, object> Args()
{
var args = base.Args();
args["name"] = Names;
args["pattern"] = Patterns;
args["type"] = Type;
return args;
}
}
[Command("galaxy info", Description = "Show galaxy version and status")]
public sealed class GalaxyInfoCommand : RoutedCommandBase { public override string Command => "galaxy"; public override string Subcommand => "info"; }
[Command("galaxy sync", Description = "Synchronize the local client with the galaxy")]
public sealed class GalaxySyncCommand : RoutedCommandBase { public override string Command => "galaxy"; public override string Subcommand => "sync"; }
[Command("galaxy cdi-version", Description = "Show galaxy CDI version")]
public sealed class GalaxyCdiVersionCommand : RoutedCommandBase { public override string Command => "galaxy"; public override string Subcommand => "cdi-version"; }
[Command("galaxy defaults get", Description = "Get a user default")]
public sealed class GalaxyDefaultsGetCommand : RoutedCommandBase
{
public override string Command => "galaxy";
public override string Subcommand => "defaults-get";
[CommandOption("default", Description = "EUserDefault enum name", IsRequired = true)]
public string Default { get; init; }
public override Dictionary<string, object> Args() { var args = base.Args(); args["default"] = Default; return args; }
}
[Command("galaxy defaults set", Description = "Set a user default")]
public sealed class GalaxyDefaultsSetCommand : ConfirmedRoutedCommandBase
{
public override string Command => "galaxy";
public override string Subcommand => "defaults-set";
[CommandOption("default", Description = "EUserDefault enum name", IsRequired = true)]
public string Default { get; init; }
[CommandOption("value", Description = "Default value", IsRequired = true)]
public string Value { get; init; }
public override Dictionary<string, object> Args() { var args = base.Args(); args["default"] = Default; args["value"] = Value; return args; }
}
[Command("galaxy backup", Description = "Back up a galaxy")]
public sealed class GalaxyBackupCommand : ConfirmedRoutedCommandBase
{
public override string Command => "galaxy";
public override string Subcommand => "backup";
[CommandOption("file", Description = "Backup file path", IsRequired = true)]
public string File { get; init; }
public override Dictionary<string, object> Args() { var args = base.Args(); args["file"] = File; args["node"] = NodeName; return args; }
}
[Command("galaxy restore", Description = "Restore a galaxy backup")]
public sealed class GalaxyRestoreCommand : ConfirmedRoutedCommandBase
{
public override string Command => "galaxy";
public override string Subcommand => "restore";
[CommandOption("file", Description = "Backup file path", IsRequired = true)]
public string File { get; init; }
[CommandOption("restore-older", Description = "Allow restoring an older version")]
public bool RestoreOlder { get; init; }
public override Dictionary<string, object> Args() { var args = base.Args(); args["file"] = File; args["node"] = NodeName; args["restore-older"] = RestoreOlder; return args; }
}
[Command("galaxy migrate", Description = "Migrate or upgrade a galaxy")]
public sealed class GalaxyMigrateCommand : ConfirmedRoutedCommandBase { public override string Command => "galaxy"; public override string Subcommand => "migrate"; public override Dictionary<string, object> Args() { var args = base.Args(); args["node"] = NodeName; return args; } }
[Command("galaxy import-objects", Description = "Import objects from an aaPKG file")]
public sealed class GalaxyImportObjectsCommand : ConfirmedRoutedCommandBase
{
public override string Command => "galaxy";
public override string Subcommand => "import-objects";
[CommandOption("file", Description = "Input file path", IsRequired = true)]
public string File { get; init; }
[CommandOption("overwrite", Description = "Allow overwrites")]
public bool Overwrite { get; init; }
public override Dictionary<string, object> Args() { var args = base.Args(); args["file"] = File; args["overwrite"] = Overwrite; return args; }
}
[Command("galaxy import-objects-ex", Description = "Import objects with conflict resolution")]
public sealed class GalaxyImportObjectsExCommand : ConfirmedRoutedCommandBase
{
public override string Command => "galaxy";
public override string Subcommand => "import-objects-ex";
[CommandOption("file", Description = "Input file path", IsRequired = true)]
public string File { get; init; }
[CommandOption("version-conflict", Description = "E_RESOLVE_VERSION_CONFLICT_ACTION enum value", IsRequired = true)]
public string VersionConflict { get; init; }
[CommandOption("name-conflict", Description = "E_RESOLVE_NAME_CONFLICT_ACTION enum value", IsRequired = true)]
public string NameConflict { get; init; }
[CommandOption("append-name", Description = "Append suffix for name conflicts")]
public string AppendName { get; init; } = "";
public override Dictionary<string, object> Args() { var args = base.Args(); args["file"] = File; args["version-conflict"] = VersionConflict; args["name-conflict"] = NameConflict; args["append-name"] = AppendName; return args; }
}
[Command("galaxy import-script-library", Description = "Import a script library")]
public sealed class GalaxyImportScriptLibraryCommand : ConfirmedRoutedCommandBase
{
public override string Command => "galaxy";
public override string Subcommand => "import-script-library";
[CommandOption("path", Description = "Script library path", IsRequired = true)]
public string Path { get; init; }
public override Dictionary<string, object> Args() { var args = base.Args(); args["path"] = Path; return args; }
}
[Command("galaxy export-all", Description = "Export all objects from a galaxy")]
public sealed class GalaxyExportAllCommand : RoutedCommandBase
{
public override string Command => "galaxy";
public override string Subcommand => "export-all";
[CommandOption("output", Description = "Output file path", IsRequired = true)]
public string Output { get; init; }
[CommandOption("export-type", Description = "EExportType enum value")]
public string ExportType { get; init; } = "exportGalaxyDump";
public override Dictionary<string, object> Args() { var args = base.Args(); args["output"] = Output; args["export-type"] = ExportType; return args; }
}
[Command("galaxy grload", Description = "Run GRLoad from a CSV file")]
public sealed class GalaxyGrloadCommand : ConfirmedRoutedCommandBase
{
public override string Command => "galaxy";
public override string Subcommand => "grload";
[CommandOption("file", Description = "CSV file path", IsRequired = true)]
public string File { get; init; }
[CommandOption("mode", Description = "GRLoadMode enum value")]
public string Mode { get; init; } = "Create";
public override Dictionary<string, object> Args() { var args = base.Args(); args["file"] = File; args["mode"] = Mode; return args; }
}
public abstract class PreLoginGalaxyCommandBase : ICommand
{
[CommandOption("node", 'n', Description = "GR node name. Blank = local node; . = local machine.")]
public string NodeName { get; init; } = "";
[CommandOption("confirm", Description = "Required for mutating commands")]
public bool Confirm { get; init; }
[CommandOption("confirm-target", Description = "Required exact target for destructive commands")]
public string ConfirmTarget { get; init; } = "";
protected IGRAccess CreateApp() => new GRAccessAppClass();
protected string Node() => GRAccessDiagnostics.NormalizeNodeName(NodeName);
public abstract ValueTask ExecuteAsync(IConsole console);
protected void RequireConfirm(string target)
{
if (!Confirm) throw new CliFx.Exceptions.CommandException("This command requires --confirm.", 1);
if (!string.Equals(ConfirmTarget, target, StringComparison.OrdinalIgnoreCase))
throw new CliFx.Exceptions.CommandException($"This command requires --confirm-target {target}.", 1);
}
}
[Command("galaxy create", Description = "Create a galaxy")]
public sealed class GalaxyCreateCommand : PreLoginGalaxyCommandBase
{
[CommandOption("galaxy", 'g', Description = "Galaxy name", IsRequired = true)]
public string GalaxyName { get; init; }
[CommandOption("security", Description = "Enable security")]
public bool Security { get; init; }
[CommandOption("auth-mode", Description = "EAuthenticationMode enum value")]
public string AuthMode { get; init; } = "aaNoAuthentication";
[CommandOption("os-user", Description = "OS user name/description")]
public string OsUser { get; init; } = "";
public override ValueTask ExecuteAsync(IConsole console)
{
RequireConfirm(GalaxyName);
var app = CreateApp();
app.CreateGalaxy(GalaxyName, Node(), Security, (EAuthenticationMode)Enum.Parse(typeof(EAuthenticationMode), AuthMode), OsUser);
console.Output.WriteLine(GRAccessDiagnostics.FormatCommandResult("CreateGalaxy", app.CommandResult));
return default;
}
}
[Command("galaxy create-from-template", Description = "Create a galaxy from a template")]
public sealed class GalaxyCreateFromTemplateCommand : PreLoginGalaxyCommandBase
{
[CommandOption("galaxy", 'g', Description = "Galaxy name", IsRequired = true)]
public string GalaxyName { get; init; }
[CommandOption("template", Description = "Create galaxy template name", IsRequired = true)]
public string Template { get; init; }
public override ValueTask ExecuteAsync(IConsole console)
{
RequireConfirm(GalaxyName);
var app = CreateApp();
app.CreateGalaxyFromTemplate(Template, GalaxyName, Node());
console.Output.WriteLine(GRAccessDiagnostics.FormatCommandResult("CreateGalaxyFromTemplate", app.CommandResult));
return default;
}
}
[Command("galaxy delete", Description = "Delete a galaxy")]
public sealed class GalaxyDeleteCommand : PreLoginGalaxyCommandBase
{
[CommandOption("galaxy", 'g', Description = "Galaxy name", IsRequired = true)]
public string GalaxyName { get; init; }
public override ValueTask ExecuteAsync(IConsole console)
{
RequireConfirm(GalaxyName);
var app = CreateApp();
app.DeleteGalaxy(GalaxyName, Node());
console.Output.WriteLine(GRAccessDiagnostics.FormatCommandResult("DeleteGalaxy", app.CommandResult));
return default;
}
}
[Command("object get", Description = "Get object details")]
public sealed class ObjectGetCommand : ObjectCommandBase { public override string Command => "object"; public override string Subcommand => "get"; }
[Command("object snapshot", Description = "Get a machine-oriented snapshot of an object, attributes, relationships, and script metadata")]
public sealed class ObjectSnapshotCommand : ObjectCommandBase { public override string Command => "object"; public override string Subcommand => "snapshot"; }
[Command("object lineage", Description = "Get object inheritance and containment lineage")]
public sealed class ObjectLineageCommand : ObjectCommandBase { public override string Command => "object"; public override string Subcommand => "lineage"; }
[Command("object children", Description = "Get contained or derived child objects")]
public sealed class ObjectChildrenCommand : ObjectCommandBase { public override string Command => "object"; public override string Subcommand => "children"; }
[Command("object query-name", Description = "Find objects by exact names")]
public sealed class ObjectQueryNameCommand : RoutedCommandBase
{
public override string Command => "object";
public override string Subcommand => "query-name";
[CommandOption("name", Description = "Object tagname. May be repeated.", IsRequired = true)]
public IReadOnlyList<string> Names { get; init; }
[CommandOption("type", 't', Description = "Object type: all, template, instance")]
public string Type { get; init; } = "all";
public override Dictionary<string, object> Args() { var args = base.Args(); args["name"] = Names; args["type"] = Type; return args; }
}
[Command("object query-condition", Description = "Find objects by one GRAccess condition")]
public sealed class ObjectQueryConditionCommand : RoutedCommandBase
{
public override string Command => "object";
public override string Subcommand => "query-condition";
[CommandOption("condition", Description = "EConditionType enum value")]
public string Condition { get; init; } = "namedLike";
[CommandOption("value", Description = "Condition value")]
public string Value { get; init; } = "%";
[CommandOption("type", 't', Description = "Object type: all, template, instance")]
public string Type { get; init; } = "all";
public override Dictionary<string, object> Args() { var args = base.Args(); args["condition"] = Condition; args["value"] = Value; args["type"] = Type; return args; }
}
[Command("object query-multi", Description = "Find objects matching any supplied pattern")]
public sealed class ObjectQueryMultiCommand : RoutedCommandBase
{
public override string Command => "object";
public override string Subcommand => "query-multi";
[CommandOption("pattern", 'p', Description = "Name pattern. May be repeated.")]
public IReadOnlyList<string> Patterns { get; init; } = Array.Empty<string>();
[CommandOption("type", 't', Description = "Object type: all, template, instance")]
public string Type { get; init; } = "all";
public override Dictionary<string, object> Args() { var args = base.Args(); args["pattern"] = Patterns; args["type"] = Type; return args; }
}
[Command("object extended-attributes", Description = "Get extended attributes for an object")]
public sealed class ObjectExtendedAttributesCommand : ObjectCommandBase
{
public override string Command => "object";
public override string Subcommand => "extended-attributes";
[CommandOption("attribute", Description = "Attribute name")]
public string Attribute { get; init; } = "";
[CommandOption("level", Description = "Hierarchy level")]
public int Level { get; init; }
public override Dictionary<string, object> Args() { var args = base.Args(); args["attribute"] = Attribute; args["level"] = Level; return args; }
}
[Command("object help-url", Description = "Get object help URL")]
public sealed class ObjectHelpUrlCommand : ObjectCommandBase { public override string Command => "object"; public override string Subcommand => "help-url"; }
[Command("object scripts list", Description = "List script-like metadata exposed on an object")]
public sealed class ObjectScriptsListCommand : ObjectCommandBase { public override string Command => "object"; public override string Subcommand => "scripts-list"; }
[Command("object scripts get", Description = "Get script metadata and body availability for an object script")]
public class ObjectScriptsGetCommand : ObjectCommandBase
{
public override string Command => "object";
public override string Subcommand => "scripts-get";
[CommandOption("script", Description = "Script name", IsRequired = true)]
public string Script { get; init; }
public override Dictionary<string, object> Args() { var args = base.Args(); args["script"] = Script; return args; }
}
[Command("object scripts set", Description = "Set an object script body when supported by the local adapter")]
public sealed class ObjectScriptsSetCommand : ObjectScriptsGetCommand
{
public override string Subcommand => "scripts-set";
[CommandOption("file", Description = "Script source file", IsRequired = true)]
public string File { get; init; }
public override Dictionary<string, object> Args() { var args = base.Args(); args["file"] = File; return args; }
}
public abstract class ObjectScriptSettingsCommandBase : ObjectScriptsGetCommand
{
[CommandOption("trigger-period-ms", Description = "Periodic script trigger interval in milliseconds")]
public string TriggerPeriodMs { get; init; } = "";
[CommandOption("trigger-type", Description = "Script trigger type value")]
public string TriggerType { get; init; } = "";
[CommandOption("expression", Description = "Script expression value")]
public string Expression { get; init; } = "";
[CommandOption("lock-trigger-period", Description = "Lock TriggerPeriod in this object after setting it")]
public bool LockTriggerPeriod { get; init; }
public override Dictionary<string, object> Args()
{
var args = base.Args();
args["trigger-period-ms"] = TriggerPeriodMs;
args["trigger-type"] = TriggerType;
args["expression"] = Expression;
args["lock-trigger-period"] = LockTriggerPeriod;
return args;
}
}
[Command("object scripts create", Description = "Create a ScriptExtension primitive and optionally initialize its body/settings")]
public sealed class ObjectScriptsCreateCommand : ObjectScriptSettingsCommandBase
{
public override string Subcommand => "scripts-create";
[CommandOption("file", Description = "Optional script source file")]
public string File { get; init; } = "";
public override Dictionary<string, object> Args() { var args = base.Args(); args["file"] = File; return args; }
}
[Command("object scripts settings set", Description = "Set ScriptExtension settings through ConfigurableAttributes")]
public sealed class ObjectScriptsSettingsSetCommand : ObjectScriptSettingsCommandBase
{
public override string Subcommand => "scripts-settings-set";
}
[Command("object checkout", Description = "Check out an object")]
public sealed class ObjectCheckoutCommand : ObjectCommandBase { public override string Command => "object"; public override string Subcommand => "checkout"; }
[Command("object checkin", Description = "Check in an object")]
public sealed class ObjectCheckinCommand : ObjectCommandBase
{
public override string Command => "object";
public override string Subcommand => "checkin";
[CommandOption("comment", Description = "Check-in comment")]
public string Comment { get; init; } = "";
public override Dictionary<string, object> Args() { var args = base.Args(); args["comment"] = Comment; return args; }
}
[Command("object undo-checkout", Description = "Undo object checkout")]
public sealed class ObjectUndoCheckoutCommand : ObjectCommandBase { public override string Command => "object"; public override string Subcommand => "undo-checkout"; }
[Command("object save", Description = "Save an object")]
public sealed class ObjectSaveCommand : ObjectCommandBase { public override string Command => "object"; public override string Subcommand => "save"; }
[Command("object unload", Description = "Unload an object from cache")]
public sealed class ObjectUnloadCommand : ObjectCommandBase { public override string Command => "object"; public override string Subcommand => "unload"; }
[Command("object set", Description = "Set an object property")]
public sealed class ObjectSetCommand : ObjectCommandBase
{
public override string Command => "object";
public override string Subcommand => "set";
[CommandOption("property", Description = "tagname, contained-name, area, host, container, toolset, security-group", IsRequired = true)]
public string Property { get; init; }
[CommandOption("value", Description = "New value", IsRequired = true)]
public string Value { get; init; }
public override Dictionary<string, object> Args() { var args = base.Args(); args["property"] = Property; args["value"] = Value; return args; }
}
[Command("template derive", Description = "Create a derived template")]
public sealed class TemplateDeriveCommand : ObjectCommandBase
{
public override string Command => "template";
public override string Subcommand => "derive";
[CommandOption("new-name", Description = "New template name", IsRequired = true)] public string NewName { get; init; }
[CommandOption("create-contained", Description = "Create contained objects")] public bool CreateContained { get; init; }
public override Dictionary<string, object> Args() { var args = base.Args(); args["new-name"] = NewName; args["create-contained"] = CreateContained; return args; }
}
[Command("template instantiate", Description = "Create an instance from a template")]
public sealed class TemplateInstantiateCommand : ObjectCommandBase
{
public override string Command => "template";
public override string Subcommand => "instantiate";
[CommandOption("new-name", Description = "New instance name", IsRequired = true)] public string NewName { get; init; }
[CommandOption("create-contained", Description = "Create contained objects")] public bool CreateContained { get; init; }
public override Dictionary<string, object> Args() { var args = base.Args(); args["new-name"] = NewName; args["create-contained"] = CreateContained; return args; }
}
[Command("template delete", Description = "Delete a template")]
public sealed class TemplateDeleteCommand : ObjectCommandBase
{
public override string Command => "template";
public override string Subcommand => "delete";
[CommandOption("force-option", Description = "EForceDeleteTemplateOption enum value")] public string ForceOption { get; init; } = "dontForceTemplateDelete";
public override Dictionary<string, object> Args() { var args = base.Args(); args["force-option"] = ForceOption; return args; }
}
public abstract class InstanceLifecycleCommandBase : ObjectCommandBase
{
[CommandOption("force-option", Description = "Delete force option")]
public string ForceOption { get; init; } = "undeployIfDeployed";
public override Dictionary<string, object> Args() { var args = base.Args(); args["force-option"] = ForceOption; return args; }
}
[Command("instance delete", Description = "Delete an instance")]
public sealed class InstanceDeleteCommand : InstanceLifecycleCommandBase { public override string Command => "instance"; public override string Subcommand => "delete"; }
[Command("instance deploy", Description = "Deploy an instance")]
public sealed class InstanceDeployCommand : ObjectCommandBase { public override string Command => "instance"; public override string Subcommand => "deploy"; }
[Command("instance undeploy", Description = "Undeploy an instance")]
public sealed class InstanceUndeployCommand : ObjectCommandBase { public override string Command => "instance"; public override string Subcommand => "undeploy"; }
[Command("instance upload", Description = "Upload runtime changes for an instance")]
public sealed class InstanceUploadCommand : ObjectCommandBase { public override string Command => "instance"; public override string Subcommand => "upload"; }
[Command("instance assign-area", Description = "Assign an instance to an area")]
public sealed class InstanceAssignAreaCommand : ObjectCommandBase
{
public override string Command => "instance";
public override string Subcommand => "assign-area";
[CommandOption("area", Description = "Area instance name", IsRequired = true)] public string Area { get; init; }
public override Dictionary<string, object> Args() { var args = base.Args(); args["area"] = Area; return args; }
}
[Command("instance assign-engine", Description = "Assign an instance to an engine")]
public sealed class InstanceAssignEngineCommand : ObjectCommandBase
{
public override string Command => "instance";
public override string Subcommand => "assign-engine";
[CommandOption("engine", Description = "Engine instance name", IsRequired = true)] public string Engine { get; init; }
public override Dictionary<string, object> Args() { var args = base.Args(); args["engine"] = Engine; return args; }
}
[Command("instance assign-container", Description = "Assign an instance to a container")]
public sealed class InstanceAssignContainerCommand : ObjectCommandBase
{
public override string Command => "instance";
public override string Subcommand => "assign-container";
[CommandOption("container", Description = "Container instance name", IsRequired = true)] public string Container { get; init; }
public override Dictionary<string, object> Args() { var args = base.Args(); args["container"] = Container; return args; }
}
[Command("io assign", Description = "Assign an I/O-related instance attribute")]
public sealed class IoAssignCommand : ConfirmedRoutedCommandBase
{
public override string Command => "io";
public override string Subcommand => "assign";
[CommandOption("name", Description = "Instance tagname", IsRequired = true)]
public string ObjectName { get; init; }
[CommandOption("attribute", Description = "Attribute name", IsRequired = true)]
public string Attribute { get; init; }
[CommandOption("value", Description = "Assigned value", IsRequired = true)]
public string Value { get; init; }
[CommandOption("data-type", Description = "string, bool, int, float, double")]
public string DataType { get; init; } = "string";
public override Dictionary<string, object> Args()
{
var args = base.Args();
args["name"] = ObjectName;
args["attribute"] = Attribute;
args["value"] = Value;
args["data-type"] = DataType;
return args;
}
}
public abstract class AreaEngineCommandBase : ConfirmedRoutedCommandBase
{
[CommandOption("template", Description = "Template to instantiate", IsRequired = true)]
public string Template { get; init; }
[CommandOption("name", Description = "New instance name", IsRequired = true)]
public string Name { get; init; }
[CommandOption("create-contained", Description = "Create contained objects")]
public bool CreateContained { get; init; }
public override Dictionary<string, object> Args()
{
var args = base.Args();
args["template"] = Template;
args["name"] = Name;
args["create-contained"] = CreateContained;
return args;
}
}
[Command("area list", Description = "List area-like instances")]
public sealed class AreaListCommand : RoutedCommandBase
{
public override string Command => "area";
public override string Subcommand => "list";
[CommandOption("pattern", 'p', Description = "Name pattern. Use % as wildcard.")] public string Pattern { get; init; } = "%Area%";
public override Dictionary<string, object> Args() { var args = base.Args(); args["pattern"] = Pattern; return args; }
}
[Command("area create", Description = "Create an area instance from a template")]
public sealed class AreaCreateCommand : AreaEngineCommandBase { public override string Command => "area"; public override string Subcommand => "create"; }
[Command("engine list", Description = "List engine-like instances")]
public sealed class EngineListCommand : RoutedCommandBase
{
public override string Command => "engine";
public override string Subcommand => "list";
[CommandOption("pattern", 'p', Description = "Name pattern. Use % as wildcard.")] public string Pattern { get; init; } = "%Engine%";
public override Dictionary<string, object> Args() { var args = base.Args(); args["pattern"] = Pattern; return args; }
}
[Command("engine create", Description = "Create an engine instance from a template")]
public sealed class EngineCreateCommand : AreaEngineCommandBase { public override string Command => "engine"; public override string Subcommand => "create"; }
public abstract class AttributeCommandBase : ObjectCommandBase
{
[CommandOption("attribute", Description = "Attribute name", IsRequired = true)]
public string Attribute { get; init; }
public override Dictionary<string, object> Args() { var args = base.Args(); args["attribute"] = Attribute; return args; }
}
[Command("object attribute get", Description = "Get one attribute")]
public sealed class ObjectAttributeGetCommand : AttributeCommandBase { public override string Command => "object"; public override string Subcommand => "attribute-get"; }
[Command("object attribute set", Description = "Set one attribute value")]
public sealed class ObjectAttributeSetCommand : AttributeCommandBase
{
public override string Command => "object";
public override string Subcommand => "attribute-set";
[CommandOption("value", Description = "New value", IsRequired = true)] public string Value { get; init; }
[CommandOption("data-type", Description = "string, bool, int, float, double")] public string DataType { get; init; } = "string";
public override Dictionary<string, object> Args() { var args = base.Args(); args["value"] = Value; args["data-type"] = DataType; return args; }
}
[Command("object attribute value get", Description = "Read back one attribute value with scalar type metadata")]
public sealed class ObjectAttributeValueGetCommand : AttributeCommandBase { public override string Command => "object"; public override string Subcommand => "attribute-value-get"; }
[Command("object attribute value set", Description = "Set one scalar attribute value")]
public sealed class ObjectAttributeValueSetCommand : AttributeCommandBase
{
public override string Command => "object";
public override string Subcommand => "attribute-value-set";
[CommandOption("value", Description = "New value", IsRequired = true)] public string Value { get; init; }
[CommandOption("data-type", Description = "string, bool, int, float, double")] public string DataType { get; init; } = "string";
public override Dictionary<string, object> Args() { var args = base.Args(); args["value"] = Value; args["data-type"] = DataType; return args; }
}
[Command("object attribute lock", Description = "Set attribute locked state")]
public sealed class ObjectAttributeLockCommand : AttributeCommandBase
{
public override string Command => "object"; public override string Subcommand => "attribute-lock";
[CommandOption("locked", Description = "MxPropertyLockedEnum value", IsRequired = true)] public string Locked { get; init; }
public override Dictionary<string, object> Args() { var args = base.Args(); args["locked"] = Locked; return args; }
}
[Command("object attribute security", Description = "Set attribute security classification")]
public sealed class ObjectAttributeSecurityCommand : AttributeCommandBase
{
public override string Command => "object"; public override string Subcommand => "attribute-security";
[CommandOption("security", Description = "MxSecurityClassification value", IsRequired = true)] public string Security { get; init; }
public override Dictionary<string, object> Args() { var args = base.Args(); args["security"] = Security; return args; }
}
[Command("object attribute buffer", Description = "Set attribute buffer flag")]
public sealed class ObjectAttributeBufferCommand : AttributeCommandBase
{
public override string Command => "object"; public override string Subcommand => "attribute-buffer";
[CommandOption("has-buffer", Description = "Whether the attribute has a buffer")] public bool HasBuffer { get; init; }
public override Dictionary<string, object> Args() { var args = base.Args(); args["has-buffer"] = HasBuffer; return args; }
}
public abstract class UdaCommandBase : ObjectCommandBase
{
[CommandOption("uda", Description = "UDA name", IsRequired = true)] public string Uda { get; init; }
[CommandOption("data-type", Description = "MxDataType value")] public string DataType { get; init; } = "MxString";
[CommandOption("category", Description = "MxAttributeCategory value")] public string Category { get; init; } = "MxCategoryWriteable_USC";
[CommandOption("security", Description = "MxSecurityClassification value")] public string Security { get; init; } = "MxSecurityUndefined";
[CommandOption("is-array", Description = "Create/update as array")] public bool IsArray { get; init; }
[CommandOption("array-count", Description = "Array element count")] public int ArrayCount { get; init; }
public override Dictionary<string, object> Args() { var args = base.Args(); args["uda"] = Uda; args["data-type"] = DataType; args["category"] = Category; args["security"] = Security; args["is-array"] = IsArray; args["array-count"] = ArrayCount; return args; }
}
[Command("object uda add", Description = "Add a UDA")]
public sealed class ObjectUdaAddCommand : UdaCommandBase { public override string Command => "object"; public override string Subcommand => "uda-add"; }
[Command("object uda delete", Description = "Delete a UDA")]
public sealed class ObjectUdaDeleteCommand : UdaCommandBase { public override string Command => "object"; public override string Subcommand => "uda-delete"; }
[Command("object uda rename", Description = "Rename a UDA")]
public sealed class ObjectUdaRenameCommand : UdaCommandBase { public override string Command => "object"; public override string Subcommand => "uda-rename"; [CommandOption("new-name", Description = "New UDA name", IsRequired = true)] public string NewName { get; init; } public override Dictionary<string, object> Args() { var args = base.Args(); args["new-name"] = NewName; return args; } }
[Command("object uda update", Description = "Update a UDA")]
public sealed class ObjectUdaUpdateCommand : UdaCommandBase { public override string Command => "object"; public override string Subcommand => "uda-update"; }
public abstract class ExtensionCommandBase : ObjectCommandBase
{
[CommandOption("extension-type", Description = "Extension type", IsRequired = true)] public string ExtensionType { get; init; }
[CommandOption("primitive", Description = "Primitive name", IsRequired = true)] public string Primitive { get; init; }
[CommandOption("object-extension", Description = "Whether this is an object extension")] public bool ObjectExtension { get; init; }
public override Dictionary<string, object> Args() { var args = base.Args(); args["extension-type"] = ExtensionType; args["primitive"] = Primitive; args["object-extension"] = ObjectExtension; return args; }
}
[Command("object extension add", Description = "Add an extension primitive")]
public sealed class ObjectExtensionAddCommand : ExtensionCommandBase { public override string Command => "object"; public override string Subcommand => "extension-add"; }
[Command("object extension delete", Description = "Delete an extension primitive")]
public sealed class ObjectExtensionDeleteCommand : ExtensionCommandBase { public override string Command => "object"; public override string Subcommand => "extension-delete"; }
[Command("object extension rename", Description = "Rename an extension primitive")]
public sealed class ObjectExtensionRenameCommand : ExtensionCommandBase { public override string Command => "object"; public override string Subcommand => "extension-rename"; [CommandOption("new-name", Description = "New primitive name", IsRequired = true)] public string NewName { get; init; } public override Dictionary<string, object> Args() { var args = base.Args(); args["new-name"] = NewName; return args; } }
[Command("objects checkout", Description = "Check out multiple objects")]
public sealed class ObjectsCheckoutCommand : BulkObjectsCommandBase { public override string Command => "objects"; public override string Subcommand => "checkout"; }
[Command("objects checkin", Description = "Check in multiple objects")]
public sealed class ObjectsCheckinCommand : BulkObjectsCommandBase { public override string Command => "objects"; public override string Subcommand => "checkin"; [CommandOption("comment", Description = "Check-in comment")] public string Comment { get; init; } = ""; public override Dictionary<string, object> Args() { var args = base.Args(); args["comment"] = Comment; return args; } }
[Command("objects undo-checkout", Description = "Undo checkout for multiple objects")]
public sealed class ObjectsUndoCheckoutCommand : BulkObjectsCommandBase { public override string Command => "objects"; public override string Subcommand => "undo-checkout"; }
[Command("objects deploy", Description = "Deploy multiple instances")]
public sealed class ObjectsDeployCommand : BulkObjectsCommandBase { public override string Command => "objects"; public override string Subcommand => "deploy"; }
[Command("objects undeploy", Description = "Undeploy multiple instances")]
public sealed class ObjectsUndeployCommand : BulkObjectsCommandBase { public override string Command => "objects"; public override string Subcommand => "undeploy"; }
[Command("objects upload", Description = "Upload multiple instances")]
public sealed class ObjectsUploadCommand : BulkObjectsCommandBase { public override string Command => "objects"; public override string Subcommand => "upload"; }
[Command("objects delete", Description = "Delete multiple objects")]
public sealed class ObjectsDeleteCommand : BulkObjectsCommandBase { public override string Command => "objects"; public override string Subcommand => "delete"; }
[Command("objects export", Description = "Export multiple objects")]
public sealed class ObjectsExportCommand : BulkObjectsCommandBase { public override string Command => "objects"; public override string Subcommand => "export"; [CommandOption("output", Description = "Output file path", IsRequired = true)] public string Output { get; init; } [CommandOption("export-type", Description = "EExportType value")] public string ExportType { get; init; } = "exportGalaxyDump"; public override Dictionary<string, object> Args() { var args = base.Args(); args["output"] = Output; args["export-type"] = ExportType; return args; } }
[Command("objects export-protected", Description = "Export multiple objects as protected")]
public sealed class ObjectsExportProtectedCommand : BulkObjectsCommandBase { public override string Command => "objects"; public override string Subcommand => "export-protected"; [CommandOption("output", Description = "Output file path", IsRequired = true)] public string Output { get; init; } public override Dictionary<string, object> Args() { var args = base.Args(); args["output"] = Output; return args; } }
[Command("toolset list", Description = "List toolsets")]
public sealed class ToolsetListCommand : RoutedCommandBase { public override string Command => "toolset"; public override string Subcommand => "list"; }
[Command("toolset tree", Description = "Show toolset tree")]
public sealed class ToolsetTreeCommand : RoutedCommandBase { public override string Command => "toolset"; public override string Subcommand => "tree"; }
[Command("toolset add", Description = "Add a toolset")]
public class ToolsetAddCommand : ConfirmedRoutedCommandBase { public override string Command => "toolset"; public override string Subcommand => "add"; [CommandOption("name", Description = "Toolset name", IsRequired = true)] public string Name { get; init; } public override Dictionary<string, object> Args() { var args = base.Args(); args["name"] = Name; return args; } }
[Command("toolset delete", Description = "Delete a toolset")]
public sealed class ToolsetDeleteCommand : ToolsetAddCommand { public override string Subcommand => "delete"; }
[Command("toolset rename", Description = "Rename a toolset")]
public sealed class ToolsetRenameCommand : ToolsetAddCommand { public override string Subcommand => "rename"; [CommandOption("new-name", Description = "New toolset name", IsRequired = true)] public string NewName { get; init; } public override Dictionary<string, object> Args() { var args = base.Args(); args["new-name"] = NewName; return args; } }
[Command("toolset move", Description = "Move a toolset")]
public sealed class ToolsetMoveCommand : ToolsetAddCommand { public override string Subcommand => "move"; [CommandOption("parent", Description = "Parent toolset name", IsRequired = true)] public string Parent { get; init; } public override Dictionary<string, object> Args() { var args = base.Args(); args["parent"] = Parent; return args; } }
[Command("script-library list", Description = "List script libraries")]
public sealed class ScriptLibraryListCommand : RoutedCommandBase { public override string Command => "script-library"; public override string Subcommand => "list"; }
[Command("script-library add", Description = "Add/import a script library")]
public class ScriptLibraryAddCommand : ConfirmedRoutedCommandBase { public override string Command => "script-library"; public override string Subcommand => "add"; [CommandOption("path", Description = "Script library path", IsRequired = true)] public string Path { get; init; } public override Dictionary<string, object> Args() { var args = base.Args(); args["path"] = Path; return args; } }
[Command("script-library import", Description = "Import a script library")]
public sealed class ScriptLibraryImportCommand : ScriptLibraryAddCommand { public override string Subcommand => "import"; }
[Command("script-library export", Description = "Export a script library")]
public sealed class ScriptLibraryExportCommand : ConfirmedRoutedCommandBase { public override string Command => "script-library"; public override string Subcommand => "export"; [CommandOption("name", Description = "Script library name", IsRequired = true)] public string Name { get; init; } [CommandOption("output", Description = "Output path", IsRequired = true)] public string Output { get; init; } public override Dictionary<string, object> Args() { var args = base.Args(); args["name"] = Name; args["output"] = Output; return args; } }
[Command("security info", Description = "Show security settings")]
public sealed class SecurityInfoCommand : RoutedCommandBase { public override string Command => "security"; public override string Subcommand => "info"; }
[Command("security roles", Description = "List security roles")]
public sealed class SecurityRolesCommand : RoutedCommandBase { public override string Command => "security"; public override string Subcommand => "roles"; }
[Command("security users", Description = "List security users")]
public sealed class SecurityUsersCommand : RoutedCommandBase { public override string Command => "security"; public override string Subcommand => "users"; }
[Command("security groups", Description = "List security groups")]
public sealed class SecurityGroupsCommand : RoutedCommandBase { public override string Command => "security"; public override string Subcommand => "groups"; }
[Command("security permissions", Description = "List permissions for a role")]
public sealed class SecurityPermissionsCommand : RoutedCommandBase { public override string Command => "security"; public override string Subcommand => "permissions"; [CommandOption("role", Description = "Role name", IsRequired = true)] public string Role { get; init; } public override Dictionary<string, object> Args() { var args = base.Args(); args["role"] = Role; return args; } }
[Command("settings locale get", Description = "Get locale settings")]
public sealed class SettingsLocaleGetCommand : RoutedCommandBase { public override string Command => "settings"; public override string Subcommand => "locale-get"; }
[Command("settings time-master get", Description = "Get time master settings")]
public sealed class SettingsTimeMasterGetCommand : RoutedCommandBase { public override string Command => "settings"; public override string Subcommand => "time-master-get"; }
}
@@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using CliFx;
using CliFx.Attributes;
using CliFx.Infrastructure;
using ArchestrA.GRAccess;
using Newtonsoft.Json;
using ZB.MOM.WW.GRAccess.Cli.GRAccess;
using ZB.MOM.WW.GRAccess.Cli.Protocol;
using ZB.MOM.WW.GRAccess.Cli.Session;
namespace ZB.MOM.WW.GRAccess.Cli.Commands.Galaxy
{
[Command("galaxy list", Description = "List available galaxies on a GR node")]
public class GalaxyListCommand : ICommand
{
[CommandOption("node", 'n', Description = "GR node name (blank = local node)")]
public string NodeName { get; init; } = "";
[CommandOption("json", Description = "Output as JSON")]
public bool Json { get; init; }
[CommandOption("llm-json", Description = "Output stable machine JSON envelope")]
public bool LlmJson { get; init; }
public async ValueTask ExecuteAsync(IConsole console)
{
// This command always runs one-shot — no galaxy login needed,
// so no session routing (session is galaxy-specific).
var galaxyNames = ListGalaxies(NodeName);
if (LlmJson)
{
await console.Output.WriteLineAsync(LlmResponse.Ok("galaxy list", string.Empty, NodeName, galaxyNames)).ConfigureAwait(false);
return;
}
if (Json)
{
var json = JsonConvert.SerializeObject(galaxyNames, Formatting.Indented);
await console.Output.WriteLineAsync(json).ConfigureAwait(false);
}
else
{
foreach (var name in galaxyNames)
await console.Output.WriteLineAsync(name).ConfigureAwait(false);
}
}
internal static List<string> ListGalaxies(string nodeName)
{
nodeName = GRAccessDiagnostics.NormalizeNodeName(nodeName);
var grAccess = new GRAccessAppClass();
var galaxies = grAccess.QueryGalaxies(nodeName);
var result = grAccess.CommandResult;
if (result != null && !result.Successful)
{
throw new InvalidOperationException(
GRAccessDiagnostics.FormatCommandResult("QueryGalaxies", result));
}
var names = new List<string>();
if (galaxies != null)
{
for (int i = 1; i <= galaxies.count; i++)
{
var galaxy = galaxies[i];
if (galaxy != null)
names.Add(galaxy.Name);
}
}
return names;
}
}
}
@@ -0,0 +1,274 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using CliFx;
using CliFx.Attributes;
using CliFx.Exceptions;
using CliFx.Infrastructure;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using ZB.MOM.WW.GRAccess.Cli.GRAccess;
using ZB.MOM.WW.GRAccess.Cli.Infrastructure;
using ZB.MOM.WW.GRAccess.Cli.Protocol;
using ZB.MOM.WW.GRAccess.Cli.Session;
namespace ZB.MOM.WW.GRAccess.Cli.Commands
{
[Command("capabilities", Description = "List machine-readable command capabilities")]
public sealed class CapabilitiesCommand : ICommand
{
[CommandOption("json", Description = "Output as JSON")]
public bool Json { get; init; }
[CommandOption("llm-json", Description = "Output stable machine JSON envelope")]
public bool LlmJson { get; init; }
public async ValueTask ExecuteAsync(IConsole console)
{
var data = new
{
Version = 1,
Commands = CommandCapabilityRegistry.Commands
};
if (LlmJson)
{
await console.Output.WriteLineAsync(LlmResponse.Ok("capabilities", string.Empty, string.Empty, data)).ConfigureAwait(false);
return;
}
if (Json)
{
await console.Output.WriteLineAsync(JsonConvert.SerializeObject(data, Formatting.Indented)).ConfigureAwait(false);
return;
}
foreach (var command in CommandCapabilityRegistry.Commands)
await console.Output.WriteLineAsync($"{command.Name}\tmutates={command.Mutates}\tsession={command.RoutesThroughSession}").ConfigureAwait(false);
}
}
[Command("validate", Description = "Validate a machine command or batch plan JSON file")]
public sealed class ValidateCommand : ICommand
{
[CommandOption("request", Description = "Plan JSON file path", IsRequired = true)]
public string Request { get; init; }
[CommandOption("llm-json", Description = "Output stable machine JSON envelope")]
public bool LlmJson { get; init; }
public async ValueTask ExecuteAsync(IConsole console)
{
var plan = BatchPlan.Load(Request);
var result = BatchValidator.Validate(plan, requireMutationConfirmation: true);
var output = new { result.Valid, Steps = result.Steps };
if (LlmJson)
await console.Output.WriteLineAsync(LlmResponse.Ok("validate", plan.Galaxy, Request, output)).ConfigureAwait(false);
else
await console.Output.WriteLineAsync(JsonConvert.SerializeObject(output, Formatting.Indented)).ConfigureAwait(false);
}
}
[Command("batch", Description = "Validate or execute a machine command plan")]
public sealed class BatchCommand : ICommand
{
[CommandOption("file", Description = "Plan JSON file path", IsRequired = true)]
public string File { get; init; }
[CommandOption("mode", Description = "validate or execute")]
public string Mode { get; init; } = "validate";
[CommandOption("llm-json", Description = "Output stable machine JSON envelope")]
public bool LlmJson { get; init; }
public async ValueTask ExecuteAsync(IConsole console)
{
var plan = BatchPlan.Load(File);
var validation = BatchValidator.Validate(plan, requireMutationConfirmation: true);
if (!string.Equals(Mode, "execute", StringComparison.OrdinalIgnoreCase))
{
await WriteBatchOutput(console, plan, new { validation.Valid, Steps = validation.Steps }, validation.Valid).ConfigureAwait(false);
return;
}
if (!validation.Valid)
{
await WriteBatchOutput(console, plan, new { validation.Valid, Steps = validation.Steps }, false).ConfigureAwait(false);
throw new CommandException("Batch validation failed.", 1);
}
var outputs = new List<object>();
for (var i = 0; i < plan.Commands.Count; i++)
{
var step = plan.Commands[i];
try
{
var output = await ExecuteStepAsync(plan, step).ConfigureAwait(false);
outputs.Add(new { Index = i, step.Command, Success = true, Output = ParseOutput(output) });
}
catch (Exception ex)
{
outputs.Add(new { Index = i, step.Command, Success = false, Error = ex.Message });
await WriteBatchOutput(console, plan, new { Valid = true, Executed = outputs }, false).ConfigureAwait(false);
throw new CommandException(ex.Message, 1);
}
}
await WriteBatchOutput(console, plan, new { Valid = true, Executed = outputs }, true).ConfigureAwait(false);
}
private async Task WriteBatchOutput(IConsole console, BatchPlan plan, object data, bool success)
{
if (LlmJson)
{
var response = new LlmResponse
{
Success = success,
Command = "batch",
Galaxy = plan.Galaxy ?? string.Empty,
Target = File ?? string.Empty,
Data = data,
ExitCode = success ? 0 : 1
};
await console.Output.WriteLineAsync(LlmResponse.Serialize(response)).ConfigureAwait(false);
return;
}
await console.Output.WriteLineAsync(JsonConvert.SerializeObject(data, Formatting.Indented)).ConfigureAwait(false);
}
private static async Task<string> ExecuteStepAsync(BatchPlan plan, BatchStep step)
{
var args = step.MaterializedArgs(plan);
args["llm-json"] = true;
args["json"] = false;
var split = CommandCapabilityRegistry.SplitCommandName(step.Command);
var galaxy = ReadString(args, "galaxy", plan.Galaxy);
var node = ReadString(args, "node", plan.Node);
if (SessionClient.TryConnect(galaxy, out var client))
{
using (client)
{
var response = await client.SendCommandAsync(PipeRequest.Execute(split.Command, split.Subcommand, args)).ConfigureAwait(false);
if (!response.Success)
throw new InvalidOperationException(response.Error);
return response.Output;
}
}
if (string.IsNullOrWhiteSpace(node))
throw new InvalidOperationException("No active session found. Provide a top-level or step node for one-shot batch execution.");
using (var connection = new GRAccessConnection(galaxy, node))
{
connection.Connect();
return GRAccessCommandDispatcher.Execute(connection.Galaxy, split.Command, split.Subcommand, args);
}
}
private static object ParseOutput(string output)
{
if (string.IsNullOrWhiteSpace(output))
return null;
try { return JToken.Parse(output); }
catch { return output; }
}
private static string ReadString(IDictionary<string, object> args, string key, string fallback)
{
return args.TryGetValue(key, out var value) && value != null
? Convert.ToString(value) ?? string.Empty
: fallback ?? string.Empty;
}
}
public sealed class BatchPlan
{
public string Galaxy { get; set; } = string.Empty;
public string Node { get; set; } = string.Empty;
public List<BatchStep> Commands { get; set; } = new List<BatchStep>();
public static BatchPlan Load(string path)
{
var root = JObject.Parse(System.IO.File.ReadAllText(path));
if (root["commands"] == null && root["command"] != null)
root = new JObject { ["commands"] = new JArray(root) };
var plan = root.ToObject<BatchPlan>() ?? new BatchPlan();
if (plan.Commands == null)
plan.Commands = new List<BatchStep>();
return plan;
}
}
public sealed class BatchStep
{
public string Command { get; set; } = string.Empty;
public Dictionary<string, object> Args { get; set; } = new Dictionary<string, object>();
public Dictionary<string, object> MaterializedArgs(BatchPlan plan)
{
var args = Args == null
? new Dictionary<string, object>()
: new Dictionary<string, object>(Args, StringComparer.OrdinalIgnoreCase);
if (!args.ContainsKey("galaxy") && !string.IsNullOrWhiteSpace(plan.Galaxy))
args["galaxy"] = plan.Galaxy;
if (!args.ContainsKey("node") && !string.IsNullOrWhiteSpace(plan.Node))
args["node"] = plan.Node;
return args;
}
}
public sealed class BatchValidationSummary
{
public bool Valid => Steps.All(s => s.Valid);
public List<BatchStepValidation> Steps { get; } = new List<BatchStepValidation>();
}
public sealed class BatchStepValidation
{
public int Index { get; set; }
public string Command { get; set; } = string.Empty;
public bool Valid { get; set; }
public IReadOnlyList<string> Errors { get; set; } = Array.Empty<string>();
public bool Mutates { get; set; }
public string ConfirmationTargetRule { get; set; } = string.Empty;
}
public static class BatchValidator
{
public static BatchValidationSummary Validate(BatchPlan plan, bool requireMutationConfirmation)
{
var summary = new BatchValidationSummary();
for (var i = 0; i < plan.Commands.Count; i++)
{
var step = plan.Commands[i];
var args = step.MaterializedArgs(plan);
var result = CommandCapabilityRegistry.Validate(step.Command, args, requireMutationConfirmation);
summary.Steps.Add(new BatchStepValidation
{
Index = i,
Command = step.Command,
Valid = result.Valid,
Errors = result.Errors,
Mutates = result.Capability?.Mutates ?? false,
ConfirmationTargetRule = result.Capability?.ConfirmTarget ?? string.Empty
});
}
if (plan.Commands.Count == 0)
summary.Steps.Add(new BatchStepValidation { Index = -1, Command = string.Empty, Valid = false, Errors = new[] { "Plan contains no commands." }, Mutates = false, ConfirmationTargetRule = string.Empty });
return summary;
}
}
}
@@ -0,0 +1,49 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using CliFx;
using CliFx.Attributes;
using CliFx.Infrastructure;
using ZB.MOM.WW.GRAccess.Cli.GRAccess;
using ZB.MOM.WW.GRAccess.Cli.Infrastructure;
namespace ZB.MOM.WW.GRAccess.Cli.Commands.Objects
{
[Command("instance list", Description = "List instances in a galaxy")]
public class InstanceListCommand : ICommand
{
[CommandOption("galaxy", 'g', Description = "Galaxy name", IsRequired = true)]
public string GalaxyName { get; init; }
[CommandOption("node", 'n', Description = "GR node name. Blank = local node; . = local machine")]
public string NodeName { get; init; } = "";
[CommandOption("pattern", 'p', Description = "Instance name pattern for GRAccess namedLike query. Use % as wildcard.")]
public string Pattern { get; init; } = "%";
[CommandOption("json", Description = "Output as JSON")]
public bool Json { get; init; }
[CommandOption("llm-json", Description = "Output stable machine JSON envelope")]
public bool LlmJson { get; init; }
public async ValueTask ExecuteAsync(IConsole console)
{
var args = new Dictionary<string, object>
{
["pattern"] = Pattern,
["json"] = Json.ToString(),
["llm-json"] = LlmJson
};
await CommandRouter.ExecuteAsync(
console,
GalaxyName,
NodeName,
"instance",
"list",
args,
galaxy => GRAccessCommandDispatcher.Execute(galaxy, "instance", "list", args))
.ConfigureAwait(false);
}
}
}
@@ -0,0 +1,57 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using CliFx;
using CliFx.Attributes;
using CliFx.Infrastructure;
using ZB.MOM.WW.GRAccess.Cli.GRAccess;
using ZB.MOM.WW.GRAccess.Cli.Infrastructure;
namespace ZB.MOM.WW.GRAccess.Cli.Commands.Objects
{
[Command("object attributes", Description = "List attributes for a template or instance")]
public class ObjectAttributesCommand : ICommand
{
[CommandOption("galaxy", 'g', Description = "Galaxy name", IsRequired = true)]
public string GalaxyName { get; init; }
[CommandOption("node", 'n', Description = "GR node name. Blank = local node; . = local machine")]
public string NodeName { get; init; } = "";
[CommandOption("name", Description = "Template or instance tagname", IsRequired = true)]
public string ObjectName { get; init; }
[CommandOption("type", 't', Description = "Object type: all, template, instance")]
public string Type { get; init; } = "all";
[CommandOption("configurable", Description = "List ConfigurableAttributes instead of all Attributes")]
public bool ConfigurableOnly { get; init; }
[CommandOption("json", Description = "Output as JSON")]
public bool Json { get; init; }
[CommandOption("llm-json", Description = "Output stable machine JSON envelope")]
public bool LlmJson { get; init; }
public async ValueTask ExecuteAsync(IConsole console)
{
var args = new Dictionary<string, object>
{
["name"] = ObjectName,
["type"] = Type,
["configurable"] = ConfigurableOnly.ToString(),
["json"] = Json.ToString(),
["llm-json"] = LlmJson
};
await CommandRouter.ExecuteAsync(
console,
GalaxyName,
NodeName,
"object",
"attributes",
args,
galaxy => GRAccessCommandDispatcher.Execute(galaxy, "object", "attributes", args))
.ConfigureAwait(false);
}
}
}
@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using CliFx;
using CliFx.Attributes;
using CliFx.Infrastructure;
using ZB.MOM.WW.GRAccess.Cli.GRAccess;
using ZB.MOM.WW.GRAccess.Cli.Infrastructure;
namespace ZB.MOM.WW.GRAccess.Cli.Commands.Objects
{
[Command("object list", Description = "List templates and/or instances in a galaxy")]
public class ObjectListCommand : ICommand
{
[CommandOption("galaxy", 'g', Description = "Galaxy name", IsRequired = true)]
public string GalaxyName { get; init; }
[CommandOption("node", 'n', Description = "GR node name. Blank = local node; . = local machine")]
public string NodeName { get; init; } = "";
[CommandOption("type", 't', Description = "Object type: all, template, instance")]
public string Type { get; init; } = "all";
[CommandOption("pattern", 'p', Description = "Name pattern for GRAccess namedLike query. Use % as wildcard.")]
public string Pattern { get; init; } = "%";
[CommandOption("json", Description = "Output as JSON")]
public bool Json { get; init; }
[CommandOption("llm-json", Description = "Output stable machine JSON envelope")]
public bool LlmJson { get; init; }
public async ValueTask ExecuteAsync(IConsole console)
{
var args = new Dictionary<string, object>
{
["type"] = Type,
["pattern"] = Pattern,
["json"] = Json.ToString(),
["llm-json"] = LlmJson
};
await CommandRouter.ExecuteAsync(
console,
GalaxyName,
NodeName,
"object",
"list",
args,
galaxy => GRAccessCommandDispatcher.Execute(galaxy, "object", "list", args))
.ConfigureAwait(false);
}
internal static GRAccessObjectKind ParseKind(string value)
{
return GRAccessQueryCommandHandler.ParseKind(value);
}
}
}
@@ -0,0 +1,49 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using CliFx;
using CliFx.Attributes;
using CliFx.Infrastructure;
using ZB.MOM.WW.GRAccess.Cli.GRAccess;
using ZB.MOM.WW.GRAccess.Cli.Infrastructure;
namespace ZB.MOM.WW.GRAccess.Cli.Commands.Objects
{
[Command("template list", Description = "List templates in a galaxy")]
public class TemplateListCommand : ICommand
{
[CommandOption("galaxy", 'g', Description = "Galaxy name", IsRequired = true)]
public string GalaxyName { get; init; }
[CommandOption("node", 'n', Description = "GR node name. Blank = local node; . = local machine")]
public string NodeName { get; init; } = "";
[CommandOption("pattern", 'p', Description = "Template name pattern for GRAccess namedLike query. Use % as wildcard.")]
public string Pattern { get; init; } = "%";
[CommandOption("json", Description = "Output as JSON")]
public bool Json { get; init; }
[CommandOption("llm-json", Description = "Output stable machine JSON envelope")]
public bool LlmJson { get; init; }
public async ValueTask ExecuteAsync(IConsole console)
{
var args = new Dictionary<string, object>
{
["pattern"] = Pattern,
["json"] = Json.ToString(),
["llm-json"] = LlmJson
};
await CommandRouter.ExecuteAsync(
console,
GalaxyName,
NodeName,
"template",
"list",
args,
galaxy => GRAccessCommandDispatcher.Execute(galaxy, "template", "list", args))
.ConfigureAwait(false);
}
}
}
@@ -0,0 +1,92 @@
using System;
using System.Diagnostics;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using CliFx;
using CliFx.Attributes;
using CliFx.Infrastructure;
using ZB.MOM.WW.GRAccess.Cli.GRAccess;
using ZB.MOM.WW.GRAccess.Cli.Protocol;
using ZB.MOM.WW.GRAccess.Cli.Session;
namespace ZB.MOM.WW.GRAccess.Cli.Commands.Session
{
[Command("session start", Description = "Start a background GRAccess session")]
public class SessionStartCommand : ICommand
{
[CommandOption("galaxy", 'g', Description = "Galaxy name", IsRequired = true)]
public string GalaxyName { get; init; }
[CommandOption("node", 'n', Description = "GR node name", IsRequired = true)]
public string NodeName { get; init; }
[CommandOption("idle-timeout", Description = "Idle timeout in minutes")]
public int IdleTimeoutMinutes { get; init; } = 30;
public async ValueTask ExecuteAsync(IConsole console)
{
var nodeName = GRAccessDiagnostics.NormalizeNodeName(NodeName);
// Check if session already running
var existing = SessionInfo.Load(GalaxyName);
if (existing != null && existing.IsAlive())
{
await console.Output.WriteLineAsync(
$"Session already running for galaxy '{GalaxyName}' (PID {existing.ProcessId})")
.ConfigureAwait(false);
return;
}
// Clean up stale session file
existing?.Delete();
// Launch self as background daemon
var exePath = Assembly.GetExecutingAssembly().Location;
var startInfo = new ProcessStartInfo
{
FileName = exePath,
Arguments = $"--daemon --galaxy \"{GalaxyName}\" --node \"{nodeName}\" --idle-timeout {IdleTimeoutMinutes}",
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
};
var process = Process.Start(startInfo);
if (process == null)
throw new InvalidOperationException("Failed to start session daemon process.");
// Wait for daemon to become ready
var deadline = DateTime.UtcNow.AddSeconds(90);
bool ready = false;
while (DateTime.UtcNow < deadline)
{
Thread.Sleep(500);
if (SessionClient.TryConnect(GalaxyName, out var client))
{
client.Dispose();
ready = true;
break;
}
// Check if daemon process exited early (connection failure, etc.)
if (process.HasExited)
{
throw new InvalidOperationException(
$"Daemon process exited with code {process.ExitCode}. Check logs for details.");
}
}
if (ready)
{
await console.Output.WriteLineAsync(
$"Session started for galaxy '{GalaxyName}' (PID {process.Id})")
.ConfigureAwait(false);
}
else
{
throw new TimeoutException(
"Session daemon did not become ready within 90 seconds. Check logs for details.");
}
}
}
}
@@ -0,0 +1,40 @@
using System.Threading.Tasks;
using CliFx;
using CliFx.Attributes;
using CliFx.Infrastructure;
using ZB.MOM.WW.GRAccess.Cli.Session;
namespace ZB.MOM.WW.GRAccess.Cli.Commands.Session
{
[Command("session status", Description = "Show GRAccess session status")]
public class SessionStatusCommand : ICommand
{
[CommandOption("galaxy", 'g', Description = "Galaxy name", IsRequired = true)]
public string GalaxyName { get; init; }
public async ValueTask ExecuteAsync(IConsole console)
{
var info = SessionInfo.Load(GalaxyName);
if (info == null)
{
await console.Output.WriteLineAsync("No session found.").ConfigureAwait(false);
return;
}
if (!info.IsAlive())
{
await console.Output.WriteLineAsync(
"Session file exists but daemon is not running (stale). Cleaning up.")
.ConfigureAwait(false);
info.Delete();
return;
}
await console.Output.WriteLineAsync($"Galaxy: {info.GalaxyName}").ConfigureAwait(false);
await console.Output.WriteLineAsync($"Node: {info.NodeName}").ConfigureAwait(false);
await console.Output.WriteLineAsync($"PID: {info.ProcessId}").ConfigureAwait(false);
await console.Output.WriteLineAsync($"Started: {info.StartedAtUtc:u}").ConfigureAwait(false);
await console.Output.WriteLineAsync($"Pipe: {info.PipeName}").ConfigureAwait(false);
}
}
}
@@ -0,0 +1,34 @@
using System.Threading.Tasks;
using CliFx;
using CliFx.Attributes;
using CliFx.Infrastructure;
using ZB.MOM.WW.GRAccess.Cli.Session;
namespace ZB.MOM.WW.GRAccess.Cli.Commands.Session
{
[Command("session stop", Description = "Stop the background GRAccess session")]
public class SessionStopCommand : ICommand
{
[CommandOption("galaxy", 'g', Description = "Galaxy name", IsRequired = true)]
public string GalaxyName { get; init; }
public async ValueTask ExecuteAsync(IConsole console)
{
if (!SessionClient.TryConnect(GalaxyName, out var client))
{
await console.Output.WriteLineAsync("No active session found.").ConfigureAwait(false);
return;
}
using (client)
{
var response = await client.SendShutdownAsync().ConfigureAwait(false);
if (response.Success)
await console.Output.WriteLineAsync("Session stopped.").ConfigureAwait(false);
else
await console.Error.WriteLineAsync($"Error stopping session: {response.Error}")
.ConfigureAwait(false);
}
}
}
}