@getmagical/cli 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -129,20 +129,25 @@ mgcl --branch-id <branch-id> workbench patch readiness --target-draft-automation
129
129
  Branch creation defaults to main. Use `--source-branch-id` and optionally
130
130
  `--source-version-id` to fork from another branch or an older version in that
131
131
  branch's history. Names must use lowercase letters or numbers separated by
132
- single dashes, such as `release-2`. Spaces, underscores, slashes, backslashes,
133
- punctuation, uppercase letters, leading or trailing dashes, and repeated dashes
134
- are rejected before an API request. Branch IDs, not names, identify targets.
132
+ single dashes, such as `release-2`, and must be 100 characters or fewer.
133
+ Spaces, underscores, slashes, backslashes, punctuation, uppercase letters,
134
+ leading or trailing dashes, repeated dashes, and over-length names are
135
+ rejected before an API request. Branch IDs, not names, identify targets, and
136
+ every automation or branch ID must be a UUID; malformed IDs are rejected
137
+ before an API request.
135
138
  Merge previews the same change set as the web UI. A conflict returns
136
- `status: "conflict"` and does not change main or delete the branch. A
137
- conflict-free merge returns `status: "merged"`, the saved-main result, and
138
- deletes the source branch. Rename, delete, compare, and publish are
139
- intentionally deferred.
139
+ `status: "conflict"`, does not change main or delete the branch, and exits
140
+ nonzero so scripts gating on the exit code never mistake it for an applied
141
+ merge. A conflict-free merge returns `status: "merged"`, the saved-main
142
+ result, and deletes the source branch. Rename, delete, compare, and publish
143
+ are intentionally deferred.
140
144
 
141
145
  The global branch target is accepted by `automations update`, `automations
142
- run-draft`, `workbench diagnostics`, `workbench schema-suggestions`, and
143
- `workbench patch preview`, `readiness`, and `apply`. It is required by
144
- `automations branches get` and `merge`. Other commands reject `--branch-id`
145
- before making an API request. Branch-aware responses identify the selected
146
+ run-draft`, `automations diff-from-published`, `workbench diagnostics`,
147
+ `workbench schema-suggestions`, and `workbench patch preview`, `readiness`,
148
+ and `apply`. It is required by `automations branches get` and `merge`. Other
149
+ commands reject `--branch-id` before making an API request, and
150
+ `mgcl --help` lists the branch-aware commands. Branch-aware responses identify the selected
146
151
  target with `branchId` and `headVersionId`. Merge returns `branchId`, its exact
147
152
  preview and conflicts, plus the new main head as `applyResult.draftVersionId`
148
153
  when successful. Branch inspection excludes workspace authentication records
package/dist/index.js CHANGED
@@ -7764,6 +7764,10 @@ function safeJsonParseWithoutLogging(json) {
7764
7764
  }
7765
7765
  }
7766
7766
 
7767
+ // ../common/dist/src/constants/automation.js
7768
+ var AUTOMATION_BRANCH_NAME_MAX_LENGTH = 100;
7769
+ var AUTOMATION_BRANCH_NAME_MAX_LENGTH_MESSAGE = `Branch name must be ${AUTOMATION_BRANCH_NAME_MAX_LENGTH} characters or fewer.`;
7770
+
7767
7771
  // ../common/dist/src/constants/tool-declarations.js
7768
7772
  import { z as z15 } from "zod";
7769
7773
 
@@ -14592,8 +14596,8 @@ var automationRunStartConfigSchema = automationRunConfigBaseSchema.omit({
14592
14596
  });
14593
14597
 
14594
14598
  // ../common/dist/src/utils/custom-instructions-command.util.js
14595
- var MAX_CUSTOM_COMMAND_COUNT = 1;
14596
- var MAX_CUSTOM_INSTRUCTIONS_LENGTH = 280;
14599
+ var MAX_CUSTOM_COMMAND_COUNT = 3;
14600
+ var MAX_CUSTOM_INSTRUCTIONS_LENGTH = 560;
14597
14601
  var STANDARD_COMMAND_NAMES = [
14598
14602
  "callSpecializedAgent",
14599
14603
  "checkpoint",
@@ -14633,22 +14637,20 @@ function assertCustomInstructionsCommand({ commands, path: path9 }) {
14633
14637
  return;
14634
14638
  }
14635
14639
  if (customCommands.length > MAX_CUSTOM_COMMAND_COUNT) {
14636
- throw createInvalidValueError(`${path9} contains ${customCommands.length.toString()} custom commands. Use at most one short custom command per agent. Split policy, decision, outreach, and multi-step work into catalog commands or child agents instead.`);
14637
- }
14638
- const [customCommand] = customCommands;
14639
- if (!customCommand) {
14640
- return;
14641
- }
14642
- const instructions = getCustomInstructions(customCommand.command);
14643
- if (instructions.length === 0) {
14644
- throw createInvalidValueError(`${customCommand.path}.arguments.instructions must be a non-empty string for custom commands.`);
14640
+ throw createInvalidValueError(`${path9} contains ${customCommands.length.toString()} custom commands. Use at most ${MAX_CUSTOM_COMMAND_COUNT.toString()} short custom commands per agent. Split policy, decision, outreach, and multi-step work into catalog commands or child agents instead.`);
14645
14641
  }
14646
- if (instructions.length > MAX_CUSTOM_INSTRUCTIONS_LENGTH) {
14647
- throw createInvalidValueError(`${customCommand.path}.arguments.instructions is ${instructions.length.toString()} characters. Custom commands must be short single-action instructions (max ${MAX_CUSTOM_INSTRUCTIONS_LENGTH.toString()} chars). Move policy/template/context text into description, inputSchema, or child agents, and express executable work with catalog commands.`);
14648
- }
14649
- const mentionedCommand = getMentionedStandardCommand(instructions);
14650
- if (mentionedCommand !== null) {
14651
- throw createInvalidValueError(`${customCommand.path}.arguments.instructions mentions "${mentionedCommand}". Use the "${mentionedCommand}" catalog command directly instead of wrapping it in custom prose.`);
14642
+ for (const customCommand of customCommands) {
14643
+ const instructions = getCustomInstructions(customCommand.command);
14644
+ if (instructions.length === 0) {
14645
+ throw createInvalidValueError(`${customCommand.path}.arguments.instructions must be a non-empty string for custom commands.`);
14646
+ }
14647
+ if (instructions.length > MAX_CUSTOM_INSTRUCTIONS_LENGTH) {
14648
+ throw createInvalidValueError(`${customCommand.path}.arguments.instructions is ${instructions.length.toString()} characters. Custom commands must be short single-action instructions (max ${MAX_CUSTOM_INSTRUCTIONS_LENGTH.toString()} chars). Move policy/template/context text into description, inputSchema, or child agents, and express executable work with catalog commands.`);
14649
+ }
14650
+ const mentionedCommand = getMentionedStandardCommand(instructions);
14651
+ if (mentionedCommand !== null) {
14652
+ throw createInvalidValueError(`${customCommand.path}.arguments.instructions mentions "${mentionedCommand}". Use the "${mentionedCommand}" catalog command directly instead of wrapping it in custom prose.`);
14653
+ }
14652
14654
  }
14653
14655
  }
14654
14656
 
@@ -14997,6 +14999,60 @@ var agentLimitationsSelection = ` limitations {
14997
14999
  maxToolCalls
14998
15000
  allowedUrlRegexes
14999
15001
  }`;
15002
+ var automationRunsInputSchema = z27.object({
15003
+ automationId: z27.string().min(1).optional(),
15004
+ from: z27.string().min(1).optional(),
15005
+ status: z27.array(z27.string().min(1)).optional(),
15006
+ invocationType: z27.array(z27.string().min(1)).optional(),
15007
+ limit: z27.number().int().positive().optional(),
15008
+ orderBy: z27.enum(["createdAtAsc", "createdAtDesc", "random"]).optional(),
15009
+ page: z27.number().int().nonnegative().optional(),
15010
+ to: z27.string().min(1).optional()
15011
+ }).refine(({ orderBy, page }) => orderBy !== "random" || (page ?? 0) === 0, {
15012
+ message: "Random ordering reshuffles on every call, so --page cannot be combined with --order-by random."
15013
+ });
15014
+ var automationRunsCliOptions = [
15015
+ stringOption({
15016
+ inputKey: "automationId",
15017
+ flagName: "automation-id",
15018
+ description: "Optional automation ID."
15019
+ }),
15020
+ stringOption({
15021
+ inputKey: "from",
15022
+ flagName: "from",
15023
+ description: "Optional inclusive createdAt lower bound ISO timestamp."
15024
+ }),
15025
+ stringArrayOption({
15026
+ inputKey: "status",
15027
+ flagName: "status",
15028
+ description: "Optional run statuses."
15029
+ }),
15030
+ stringArrayOption({
15031
+ inputKey: "invocationType",
15032
+ flagName: "invocation-type",
15033
+ description: "Optional invocation types."
15034
+ }),
15035
+ numberOption({
15036
+ inputKey: "limit",
15037
+ flagName: "limit",
15038
+ description: "Maximum number of runs to return."
15039
+ }),
15040
+ stringOption({
15041
+ inputKey: "orderBy",
15042
+ flagName: "order-by",
15043
+ description: "Run ordering. Use random for sampling; it reshuffles on every call."
15044
+ }),
15045
+ numberOption({
15046
+ inputKey: "page",
15047
+ flagName: "page",
15048
+ description: "Zero-based page number."
15049
+ }),
15050
+ stringOption({
15051
+ inputKey: "to",
15052
+ flagName: "to",
15053
+ description: "Optional inclusive createdAt upper bound ISO timestamp."
15054
+ })
15055
+ ];
15000
15056
  var operationSpecs = [
15001
15057
  createOperationSpec({
15002
15058
  operationName: "GetCaptureContext",
@@ -15344,12 +15400,14 @@ query GetAutomationAgents($ids: [ID!]!) {
15344
15400
  operationName: "GetAutomationDiffFromPublished",
15345
15401
  kind: "query",
15346
15402
  inputSchema: z27.object({
15347
- automationId: z27.string().min(1)
15403
+ automationId: z27.uuid("--automation-id must be a UUID."),
15404
+ branchId: z27.uuid("--branch-id must be a UUID.").optional()
15348
15405
  }),
15349
15406
  cli: {
15407
+ branchContext: "optional",
15350
15408
  name: "get-automation-diff-from-published",
15351
15409
  path: ["automations", "diff-from-published"],
15352
- description: "Fetch draft-vs-published automation JSON Patch.",
15410
+ description: "Fetch draft-vs-published automation JSON Patch for main or one branch.",
15353
15411
  resultKey: "automationDiffFromPublished",
15354
15412
  options: [
15355
15413
  stringOption({
@@ -15361,9 +15419,10 @@ query GetAutomationAgents($ids: [ID!]!) {
15361
15419
  ]
15362
15420
  },
15363
15421
  mcp: { fileName: "automationDiffFromPublished.graphql" },
15364
- document: `# Query JSON Patch changes from published automation JSON to draft automation JSON
15365
- query GetAutomationDiffFromPublished($automationId: ID!) {
15366
- automationDiffFromPublished(automationId: $automationId) {
15422
+ document: `# Query JSON Patch changes from published automation JSON to draft automation JSON.
15423
+ # Pass branchId to diff a branch head against published instead of the main draft.
15424
+ query GetAutomationDiffFromPublished($automationId: ID!, $branchId: ID) {
15425
+ automationDiffFromPublished(automationId: $automationId, branchId: $branchId) {
15367
15426
  automationId
15368
15427
  automationName
15369
15428
  mode
@@ -15686,57 +15745,13 @@ query InspectPausedAutomationStep($runId: ID!) {
15686
15745
  createOperationSpec({
15687
15746
  operationName: "GetAutomationRuns",
15688
15747
  kind: "query",
15689
- inputSchema: z27.object({
15690
- automationId: z27.string().min(1).optional(),
15691
- from: z27.string().min(1).optional(),
15692
- status: z27.array(z27.string().min(1)).optional(),
15693
- invocationType: z27.array(z27.string().min(1)).optional(),
15694
- limit: z27.number().int().positive().optional(),
15695
- page: z27.number().int().nonnegative().optional(),
15696
- to: z27.string().min(1).optional()
15697
- }),
15748
+ inputSchema: automationRunsInputSchema,
15698
15749
  cli: {
15699
15750
  name: "get-automation-runs",
15700
15751
  path: ["automation-runs", "list"],
15701
15752
  description: "Fetch automation run history.",
15702
15753
  resultKey: "automationRuns",
15703
- options: [
15704
- stringOption({
15705
- inputKey: "automationId",
15706
- flagName: "automation-id",
15707
- description: "Optional automation ID."
15708
- }),
15709
- stringOption({
15710
- inputKey: "from",
15711
- flagName: "from",
15712
- description: "Optional inclusive createdAt lower bound ISO timestamp."
15713
- }),
15714
- stringArrayOption({
15715
- inputKey: "status",
15716
- flagName: "status",
15717
- description: "Optional run statuses."
15718
- }),
15719
- stringArrayOption({
15720
- inputKey: "invocationType",
15721
- flagName: "invocation-type",
15722
- description: "Optional invocation types."
15723
- }),
15724
- numberOption({
15725
- inputKey: "limit",
15726
- flagName: "limit",
15727
- description: "Maximum number of runs to return."
15728
- }),
15729
- numberOption({
15730
- inputKey: "page",
15731
- flagName: "page",
15732
- description: "Zero-based page number."
15733
- }),
15734
- stringOption({
15735
- inputKey: "to",
15736
- flagName: "to",
15737
- description: "Optional inclusive createdAt upper bound ISO timestamp."
15738
- })
15739
- ]
15754
+ options: automationRunsCliOptions
15740
15755
  },
15741
15756
  mcp: { fileName: "automationRuns.graphql" },
15742
15757
  document: `# Query automation run history with optional automation and time-window filters
@@ -15746,6 +15761,7 @@ query GetAutomationRuns(
15746
15761
  $status: [RunStatus!]
15747
15762
  $invocationType: [InvocationType!]
15748
15763
  $limit: Int
15764
+ $orderBy: AutomationRunOrderBy
15749
15765
  $page: Int
15750
15766
  $to: String
15751
15767
  ) {
@@ -15755,6 +15771,7 @@ query GetAutomationRuns(
15755
15771
  status: $status
15756
15772
  invocationType: $invocationType
15757
15773
  limit: $limit
15774
+ orderBy: $orderBy
15758
15775
  page: $page
15759
15776
  to: $to
15760
15777
  ) {
@@ -15770,6 +15787,59 @@ query GetAutomationRuns(
15770
15787
  pausedAt
15771
15788
  finishedAt
15772
15789
  }
15790
+ }`
15791
+ }),
15792
+ createOperationSpec({
15793
+ operationName: "GetAutomationRunsPage",
15794
+ kind: "query",
15795
+ inputSchema: automationRunsInputSchema,
15796
+ cli: {
15797
+ name: "get-automation-runs-page",
15798
+ path: ["automation-runs", "list-page"],
15799
+ description: "Fetch automation run history with bounded total metadata. When isTotalTruncated is true, total is a lower bound.",
15800
+ resultKey: "automationRunsPage",
15801
+ options: automationRunsCliOptions
15802
+ },
15803
+ mcp: { fileName: "automationRunsPage.graphql" },
15804
+ document: `# Query automation run history with optional filters and a bounded total
15805
+ query GetAutomationRunsPage(
15806
+ $automationId: ID
15807
+ $from: String
15808
+ $status: [RunStatus!]
15809
+ $invocationType: [InvocationType!]
15810
+ $limit: Int
15811
+ $orderBy: AutomationRunOrderBy
15812
+ $page: Int
15813
+ $to: String
15814
+ ) {
15815
+ automationRunsPage(
15816
+ automationId: $automationId
15817
+ from: $from
15818
+ status: $status
15819
+ invocationType: $invocationType
15820
+ limit: $limit
15821
+ orderBy: $orderBy
15822
+ page: $page
15823
+ to: $to
15824
+ ) {
15825
+ items {
15826
+ id
15827
+ automationId
15828
+ parentAutomationRunId
15829
+ status
15830
+ statusDetail
15831
+ invocationType
15832
+ summary
15833
+ createdAt
15834
+ startedAt
15835
+ pausedAt
15836
+ finishedAt
15837
+ }
15838
+ total
15839
+ page
15840
+ pageSize
15841
+ isTotalTruncated
15842
+ }
15773
15843
  }`
15774
15844
  }),
15775
15845
  createOperationSpec({
@@ -15912,7 +15982,7 @@ query ViewAutomationReview($automationId: ID!) {
15912
15982
  operationName: "ListAutomationBranches",
15913
15983
  kind: "query",
15914
15984
  inputSchema: z27.object({
15915
- automationId: z27.string().min(1)
15985
+ automationId: z27.uuid("--automation-id must be a UUID.")
15916
15986
  }),
15917
15987
  cli: {
15918
15988
  name: "list-automation-branches",
@@ -15947,10 +16017,13 @@ query ListAutomationBranches($automationId: ID!) {
15947
16017
  operationName: "CreateAutomationBranch",
15948
16018
  kind: "mutation",
15949
16019
  inputSchema: z27.object({
15950
- automationId: z27.string().min(1),
15951
- name: z27.string().regex(AUTOMATION_BRANCH_NAME_PATTERN, AUTOMATION_BRANCH_NAME_MESSAGE),
15952
- sourceBranchId: z27.string().min(1).optional(),
15953
- sourceVersionId: z27.string().min(1).optional()
16020
+ automationId: z27.uuid("--automation-id must be a UUID."),
16021
+ name: z27.string().regex(AUTOMATION_BRANCH_NAME_PATTERN, AUTOMATION_BRANCH_NAME_MESSAGE).max(
16022
+ AUTOMATION_BRANCH_NAME_MAX_LENGTH,
16023
+ AUTOMATION_BRANCH_NAME_MAX_LENGTH_MESSAGE
16024
+ ),
16025
+ sourceBranchId: z27.uuid("--source-branch-id must be a UUID.").optional(),
16026
+ sourceVersionId: z27.uuid("--source-version-id must be a UUID.").optional()
15954
16027
  }),
15955
16028
  cli: {
15956
16029
  name: "create-automation-branch",
@@ -16011,8 +16084,8 @@ mutation CreateAutomationBranch(
16011
16084
  operationName: "GetAutomationBranch",
16012
16085
  kind: "query",
16013
16086
  inputSchema: z27.object({
16014
- automationId: z27.string().min(1),
16015
- branchId: z27.string().min(1)
16087
+ automationId: z27.uuid("--automation-id must be a UUID."),
16088
+ branchId: z27.uuid("--branch-id must be a UUID.")
16016
16089
  }),
16017
16090
  cli: {
16018
16091
  branchContext: "required",
@@ -16043,14 +16116,14 @@ query GetAutomationBranch($automationId: ID!, $branchId: ID!) {
16043
16116
  operationName: "MergeAutomationBranch",
16044
16117
  kind: "mutation",
16045
16118
  inputSchema: z27.object({
16046
- automationId: z27.string().min(1),
16047
- branchId: z27.string().min(1)
16119
+ automationId: z27.uuid("--automation-id must be a UUID."),
16120
+ branchId: z27.uuid("--branch-id must be a UUID.")
16048
16121
  }),
16049
16122
  cli: {
16050
16123
  branchContext: "required",
16051
16124
  name: "merge-automation-branch",
16052
16125
  path: ["automations", "branches", "merge"],
16053
- description: "Preview and merge a branch into main when every change is conflict-free.",
16126
+ description: "Preview and merge a branch into main when every change is conflict-free. Exits nonzero when conflicts block the merge.",
16054
16127
  resultKey: "mergeAutomationBranch",
16055
16128
  options: [
16056
16129
  stringOption({
@@ -18523,7 +18596,7 @@ var pathwayRegressionAgentVersionRegressionRunResponseSchema = z28.object({
18523
18596
  automationVersionId: z28.string().min(1),
18524
18597
  baselineAgentVersionId: z28.string().min(1),
18525
18598
  baselineAutomationVersionId: z28.string().min(1),
18526
- baselineContractId: z28.string().min(1),
18599
+ baselineContractId: z28.string().min(1).nullable(),
18527
18600
  candidateContractId: z28.string().min(1),
18528
18601
  contractId: z28.string().min(1),
18529
18602
  paths: z28.array(pathwayRegressionAgentVersionRunPathSchema),
@@ -19401,6 +19474,32 @@ function configureCommandPresentation(command) {
19401
19474
  command.createHelp = () => new MagicalHelp();
19402
19475
  return command;
19403
19476
  }
19477
+ function formatBranchAwareCommandsHelp() {
19478
+ const branchAwareCommandLines = operationSpecs.flatMap((operationSpec) => {
19479
+ const { branchContext } = operationSpec.cli;
19480
+ if (branchContext === void 0) {
19481
+ return [];
19482
+ }
19483
+ return [
19484
+ `mgcl ${operationSpec.cli.path.join(" ")} (--branch-id ${branchContext})`
19485
+ ];
19486
+ }).toSorted();
19487
+ return formatExamples("Branch-aware commands", branchAwareCommandLines);
19488
+ }
19489
+ function formatRootAfterHelpText() {
19490
+ return `
19491
+ ${formatExamples("Quick start", [
19492
+ "mgcl auth login",
19493
+ "mgcl org list",
19494
+ "mgcl automations list --limit 10",
19495
+ "mgcl automation-runs get --id <run-id>",
19496
+ "mgcl update"
19497
+ ])}
19498
+
19499
+ ${formatBranchAwareCommandsHelp()}
19500
+
19501
+ ${formatOutputModesNote()}`;
19502
+ }
19404
19503
  function createOperationHelpExamples(operationSpec) {
19405
19504
  const commandPath = operationSpec.cli.path.join(" ");
19406
19505
  const baseCommand = operationSpec.cli.branchContext === "required" ? `mgcl --branch-id <branch-id> ${commandPath}` : `mgcl ${commandPath}`;
@@ -21840,6 +21939,7 @@ async function handleOperationCommand(dependencies, operationSpec, commandOption
21840
21939
  json: commandOptions.json,
21841
21940
  result
21842
21941
  });
21942
+ assertBranchMergeApplied(operationSpec, result);
21843
21943
  if (operationSpec.operationName === "ViewAutomationReview" && commandOptions["open"] && isRecord5(result) && typeof result["reportUrl"] === "string") {
21844
21944
  await dependencies.openExternalUrl(result["reportUrl"]).catch((error) => {
21845
21945
  dependencies.io.error(
@@ -21848,6 +21948,13 @@ async function handleOperationCommand(dependencies, operationSpec, commandOption
21848
21948
  });
21849
21949
  }
21850
21950
  }
21951
+ function assertBranchMergeApplied(operationSpec, result) {
21952
+ if (operationSpec.operationName === "MergeAutomationBranch" && (!isRecord5(result) || result["status"] !== "merged")) {
21953
+ throw new CliError(
21954
+ "Merge not applied. Review the merge output, resolve any conflicts, then merge again."
21955
+ );
21956
+ }
21957
+ }
21851
21958
  function getOperation(operationName) {
21852
21959
  const operationSpec = operationSpecs.find(
21853
21960
  (candidate) => candidate.operationName === operationName
@@ -22835,26 +22942,14 @@ function buildProgram(dependencies) {
22835
22942
  ).addOption(new Option3("--json", "Render JSON output.")).addOption(
22836
22943
  new Option3(
22837
22944
  "--branch-id <id>",
22838
- "Target a supported automation command at one branch; omit it to target main."
22945
+ 'Target a supported automation command at one branch; omit it to target main. "Branch-aware commands" below lists the commands that accept it.'
22839
22946
  )
22840
22947
  ).version(CLI_VERSION, "--version", `Display mgcl version (${CLI_VERSION}).`).addOption(
22841
22948
  new Option3(
22842
22949
  "--update",
22843
22950
  "Update mgcl to the latest published npm release."
22844
22951
  )
22845
- ).showHelpAfterError().showSuggestionAfterError().addHelpText(
22846
- "after",
22847
- `
22848
- ${formatExamples("Quick start", [
22849
- "mgcl auth login",
22850
- "mgcl org list",
22851
- "mgcl automations list --limit 10",
22852
- "mgcl automation-runs get --id <run-id>",
22853
- "mgcl update"
22854
- ])}
22855
-
22856
- ${formatOutputModesNote()}`
22857
- );
22952
+ ).showHelpAfterError().showSuggestionAfterError().addHelpText("after", formatRootAfterHelpText());
22858
22953
  program2.hook("preAction", (_thisCommand, actionCommand) => {
22859
22954
  const options = actionCommand.optsWithGlobals();
22860
22955
  const branchContext = commandBranchContexts.get(actionCommand);