Editing an Argus command

How the Argus command editor is organised — Component, C# Blocks, and Utils — and how to edit each part.

An Argus command is edited entirely inside the built-in command editor. When you open a command for editing, Argus shows a three-column layout:

  • a left rail listing every block that makes up the command,
  • a code editor in the middle for whichever block you selected, and
  • a live preview panel on the right that renders the panel exactly as it will appear in the Argus chat.

At the top of the editor you’ll see three actions — Preview, Validate, and Save — plus the name of the command you’re editing.

The Argus command editor: left rail with Component, C# Blocks and Utils; code editor in the middle with an Insert toolbar; live preview panel on the right; Preview / Validate / Save buttons in the top-right.

Anatomy of a command

At a glance, a command is a Component + one or more C# Blocks (+ optional Utils) bundled together. The Component renders in the Argus chat panel; the C# Blocks execute inside Autodesk Revit.

Animated diagram: Component.tsx, main.cs, load-*.cs and utils.cs flow into a central Argus Command, which then runs in both the Argus Chat Panel and Autodesk Revit.

What you can edit

The left rail groups the command into three sections:

  • Component — the React panel users see in the chat
  • C# Blocks — the Revit-side scripts that actually do the work
  • Utils — optional shared helpers for the C# blocks

Everything else about the command (its identifier, Command Library icon, help link, supported Revit versions) is managed by Argus for you. You never edit those directly.

Component

The Component block is a single Component.tsx file. It renders the input form users fill in and calls the C# blocks to run the actual work. Every command has exactly one component and it exports a default function called GeneratedCommand:

import * as React from "react";
import { execute } from "@/lib/revit-execute";
import { Card } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Combobox } from "@/components/ui/combobox";

export default function GeneratedCommand() {
  const [running, setRunning] = React.useState(false);
  const [error,   setError]   = React.useState<string | null>(null);

  // Inputs the user fills in
  const [dimensionType, setDimensionType] = React.useState<string>("");

  // Options loaded from the model on mount
  const [dimensionTypeOptions, setDimensionTypeOptions] = React.useState<
    Array<{ value: string; label: string }>
  >([]);

  React.useEffect(() => {
    (async () => {
      const r = await execute(runtime.getCode("load-dimension-type"));
      const out = r.output as { items?: Array<{ id: string; name: string }> } | null;
      if (r.ok && out?.items) {
        setDimensionTypeOptions(out.items.map(x => ({ value: x.id, label: x.name })));
      }
    })();
  }, []);

  const onRun = async () => {
    setRunning(true);
    setError(null);
    const code = runtime.getCode("main", { dimensionType: Number(dimensionType) });
    const r = await execute(code);
    if (!r.ok) setError(r.error ?? "Execution failed.");
    setRunning(false);
  };

  return (
    <Card className="p-4 flex flex-col gap-3">
      <Label>Dimension Type</Label>
      <Combobox
        options={dimensionTypeOptions}
        value={dimensionType}
        onValueChange={setDimensionType}
        placeholder="Select a type"
      />
      <Button onClick={onRun} disabled={running || !dimensionType}>
        {running ? "Creating…" : "Create dimensions"}
      </Button>
      {error && <p className="text-red-500 text-sm">{error}</p>}
    </Card>
  );
}

Two runtime helpers are always available inside the component:

Helper Purpose
runtime.getCode(blockName, values?) Compiles the named C# block (e.g. "main" or "load-dimension-type") into a payload, injecting typed input values from the UI.
execute(code) Sends the payload to Revit and returns { ok, output, error }.

Inserting a C# block from the toolbar

Above the code editor there’s an Insert toolbar showing every C# block in the command (for example load-dimension-type and main). Clicking one drops a ready-made runtime.getCode("…") call at your cursor position so you don’t have to remember the block name.

The Argus UI kit

The preview panel renders your component using Argus’s built-in UI kit. Common imports:

import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox";
import { Select, SelectContent, SelectGroup, SelectItem,
         SelectTrigger, SelectValue } from "@/components/ui/select";
import { Combobox } from "@/components/ui/combobox";
import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert";
import { Badge } from "@/components/ui/badge";
import { Response } from "@/components/ui/response"; // renders Markdown from Logger

Use Combobox for searchable dropdowns, Select for short static lists, and Checkbox for booleans. Group related toggles inside nested <Card> blocks to keep the panel scannable.

C# Blocks

C# Blocks are the scripts that actually run inside Revit. Every command has one main block; commands that need to populate dropdowns from the current model also have one or more load-… blocks (for example load-dimension-type, load-grid-type, load-source-categories).

Every C# block follows the same shape: an Inputs class, an Output class, and a Run(Inputs input) method.

main — the block that does the work

using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using System.Linq;

class Inputs
{
    public string[] VerticalNames   { get; set; }
    public string[] HorizontalNames { get; set; }
    public long?    GridType        { get; set; }
    public string   StartX          { get; set; }
    public string   StartY          { get; set; }
}

class Output
{
    public int VerticalGridCount   { get; set; }
    public int HorizontalGridCount { get; set; }
}

Output Run(Inputs input)
{
    Document doc = UIDoc.Document;

    // …use input.VerticalNames, input.GridType, etc. to do the work…

    return new Output
    {
        VerticalGridCount   = input.VerticalNames.Length,
        HorizontalGridCount = input.HorizontalNames.Length,
    };
}

Rules for Inputs:

  • Every value the component collects becomes a public property.
  • Use PascalCase — the component sends camelCase and Argus converts automatically (dimensionType in TSX → DimensionType in C#).
  • Supported types: string, int, int?, long, long?, double, double?, bool, bool?, plus arrays of any of those.
  • Use nullable types for optional inputs.

Rules for Output:

  • Anything you want to send back to the component (counts, ids, messages) goes here.
  • The component reads it as r.output.

load-… — dropdown loaders

Whenever a form needs a dropdown filled with data from the current model, add a load-… block next to main. It uses the same shape but doesn’t modify the model:

class Inputs { }

class GridType
{
    public string Id   { get; set; }
    public string Name { get; set; }
}

class Output
{
    public GridType[] Items { get; set; }
}

Output Run(Inputs input)
{
    Document doc = UIDoc.Document;
    var items = new FilteredElementCollector(doc)
        .OfCategory(BuiltInCategory.OST_Grids)
        .WhereElementIsElementType()
        .Select(e => new GridType { Id = e.Id.ToString(), Name = e.Name })
        .OrderBy(x => x.Name)
        .ToArray();
    return new Output { Items = items };
}

The component then calls execute(runtime.getCode("load-grid-type")) on mount and hands the returned items to a Combobox.

Global context in every C# block

Inside every Run(...) you have a few helpers injected automatically — no using needed beyond the Revit namespaces:

Symbol Type Purpose
UIApp UIApplication Application-level access (settings, active add-ins).
UIDoc UIDocument Active document, selection, and view.
Logger StringBuilder Append Markdown that surfaces in the Argus output panel.
CheckCancellationRequested() void Call inside loops so the user can cancel gracefully.
ProgressUpdate(state, steps, total) void Report progress back to the chat panel.

Do not declare or initialise these — they’re provided for you.

Utils

The Utils section holds shared C# helpers for your main and load-… blocks. Argus compiles everything in the command together, so any class or method you define here is visible from every C# block.

Reach for utils when the same piece of logic — a geometry helper, a parameter reader, a formatter — is needed in more than one block.

Preview, Validate, Save

Once you’ve edited the Component and any C# blocks, use the three buttons in the top-right of the editor:

  • Preview — refreshes the live preview panel on the right using your latest code. Users on the receiving end see this exact panel in the chat.
  • Validate — statically checks the command: block IDs, Inputs/Output shapes, TypeScript compile, missing imports, and naming rules. Errors appear as a banner along the bottom of the editor (for example, “Block id should be lowercase kebab-case”).
  • Save — commits your changes. If the command is published to a team library, saved changes propagate to your teammates the next time they open the command.

Wiring inputs between Component and Main

The keys you pass to runtime.getCode("main", { … }) map directly to the Inputs class of the main block:

runtime.getCode("main", {
  verticalNames: ["A", "B", "C"], // → input.VerticalNames  (string[])
  gridType: 123456,               // → input.GridType       (long)
  startX: "0",                    // → input.StartX         (string)
});

When you add a new input, change it in three places:

  1. Add a property on the Inputs class in the main block.
  2. Add React state for it in Component.tsx.
  3. Include it in the object passed to runtime.getCode("main", { … }).

Typical editing workflow

  1. Open the command in the editor.
  2. Change what the model does → open the main block, edit the C#. Update Inputs/Output as needed.
  3. Change what the panel looks like → open the Component, edit the TSX. Add/remove state and update the keys you pass to runtime.getCode("main", { … }).
  4. Change dropdown data → open the matching load-… block, or add a new one from the left rail. Fetch it from the component with execute(runtime.getCode("load-…")).
  5. Extract anything shared into Utils.
  6. Click Preview to see the panel refresh, Validate to catch mistakes, then Save.