37 KiB
Dynamic Step Manipulation & Workflow Export
Status: Draft
Author: GitHub Actions Team
Date: January 2026
Prerequisites: dap-debugging.md, dap-step-backwards.md (completed)
Progress Checklist
- Chunk 1: Command Parser & Infrastructure
- Chunk 2: Step Serializer (ActionStep → YAML)
- Chunk 3: Step Factory (Create new steps)
- Chunk 4: Step Manipulator (Queue operations)
- Chunk 5: REPL Commands (steps list, steps add run, steps edit, steps remove, steps move)
- Chunk 6: Action Download Integration (steps add uses)
- Chunk 7: Export Command (steps export)
- Chunk 8: Output Format Flag (--output text|json for programmatic use)
- Chunk 9: Browser Extension UI
Overview
This plan extends the DAP debugger with the ability to dynamically manipulate job steps during a debug session: add new steps, edit pending steps, remove steps, and reorder them. At the end of a session, users can export the modified steps as YAML to paste into their workflow file.
This transforms the debugger from a "read-only inspection tool" into an interactive workflow editor — the key differentiator of this prototype.
Goals
- Primary: Enable add/edit/move/delete of job steps during debug session
- Primary: Support both
runandusesstep types - Primary: Export modified steps as valid YAML
- Secondary: Provide
--outputflag for text/JSON response format - Non-goal: Full workflow file reconstruction (steps section only)
- Non-goal: Production action restriction enforcement (noted for later)
Command API Specification
Grammar
steps <command> [target] [options] [--output text|json]
Output Format
All commands support the --output flag to control response format:
--output text(default) - Human-readable text output--output json- JSON output for programmatic use- Short form:
-o json,-o text - Equals form:
--output=json,--output=text
Index Reference
- 1-based indexing for user-friendliness
- Completed steps are shown but read-only
- Cannot modify currently executing step (except via step-back)
Commands Summary
| Command | Purpose | Example |
|---|---|---|
steps list |
Show all steps | steps list --verbose |
steps add |
Add new step | steps add run "npm test" --after 3 |
steps edit |
Modify step | steps edit 4 --script "npm run test:ci" |
steps remove |
Delete step | steps remove 5 |
steps move |
Reorder step | steps move 5 --after 2 |
steps export |
Generate YAML | steps export --with-comments |
Position Modifiers
For steps add and steps move:
--at <index>— Insert at specific position--after <index>— Insert after step--before <index>— Insert before step--first— Insert at first pending position--last— Insert at end (default)
Full Command Reference
See "Command API Full Reference" section at end of document.
Implementation Chunks
Chunk 1: Command Parser & Infrastructure
Goal: Create the foundation for parsing and dispatching step commands.
Files to create:
src/Runner.Worker/Dap/StepCommands/StepCommandParser.cssrc/Runner.Worker/Dap/StepCommands/StepCommandResult.cs
Files to modify:
src/Runner.Worker/Dap/DapDebugSession.cs— Add command dispatch inHandleEvaluate()
Details:
-
StepCommandParser — Parse REPL command strings into structured commands:
public interface IStepCommandParser { StepCommand Parse(string input); // "steps add run \"echo hello\" --after 3" bool IsStepCommand(string input); // Starts with "steps " or equals "steps" } public enum OutputFormat { Text, Json } public abstract class StepCommand { public OutputFormat Output { get; set; } = OutputFormat.Text; } public class ListCommand : StepCommand { public bool Verbose; } public class AddRunCommand : StepCommand { public string Script; public string Name; public string Shell; public StepPosition Position; // ... } public class AddUsesCommand : StepCommand { /* ... */ } public class EditCommand : StepCommand { /* ... */ } public class RemoveCommand : StepCommand { public int Index; } public class MoveCommand : StepCommand { public int FromIndex; public StepPosition Position; } public class ExportCommand : StepCommand { public bool ChangesOnly; public bool WithComments; } -
StepPosition — Represent insertion position:
public class StepPosition { public PositionType Type { get; set; } // At, After, Before, First, Last public int? Index { get; set; } // For At, After, Before } -
StepCommandResult — Standardized response:
public class StepCommandResult { public bool Success { get; set; } public string Message { get; set; } public string Error { get; set; } // Error code public object Result { get; set; } // Command-specific data } -
Integration in DapDebugSession.HandleEvaluate():
// After checking for !debug command if (_stepCommandParser.IsStepCommand(expression)) { return await HandleStepCommandAsync(expression, executionContext); }
Testing:
- Unit tests for command parsing
- Test various edge cases (quoted strings, escapes, missing args)
Estimated effort: Small-medium
Chunk 2: Step Serializer (ActionStep → YAML)
Goal: Convert Pipelines.ActionStep objects to YAML string representation.
Files to create:
src/Runner.Worker/Dap/StepCommands/StepSerializer.cs
Details:
-
IStepSerializer interface:
public interface IStepSerializer { string ToYaml(Pipelines.ActionStep step); string ToYaml(IEnumerable<StepInfo> steps, bool withComments = false); } -
Handle both step types:
For
runsteps (ScriptReference):- name: Run Tests run: | npm ci npm test shell: bash working-directory: src env: NODE_ENV: test if: success() continue-on-error: false timeout-minutes: 10For
usessteps (RepositoryPathReference):- name: Setup Node uses: actions/setup-node@v4 with: node-version: '20' cache: npm env: NODE_OPTIONS: --max-old-space-size=4096 if: success() -
Extract data from ActionStep:
Referencetype determines run vs usesInputsTemplateToken contains script (for run) or with values (for uses)EnvironmentTemplateToken contains env varsConditionstring contains if expressionDisplayNameorDisplayNameTokenfor name
-
Use existing YAML infrastructure:
- Consider using
YamlObjectWriterfrom WorkflowParser - Or use a simple string builder for more control
- Consider using
Testing:
- Round-trip tests: create step → serialize → verify YAML
- Test all step properties
- Test multi-line scripts
Estimated effort: Medium
Chunk 3: Step Factory (Create new steps)
Goal: Create Pipelines.ActionStep and IActionRunner objects at runtime.
Files to create:
src/Runner.Worker/Dap/StepCommands/StepFactory.cs
Details:
-
IStepFactory interface:
public interface IStepFactory : IRunnerService { Pipelines.ActionStep CreateRunStep( string script, string name = null, string shell = null, string workingDirectory = null, Dictionary<string, string> env = null, string condition = null, bool continueOnError = false, int? timeoutMinutes = null); Pipelines.ActionStep CreateUsesStep( string actionReference, // "owner/repo@ref" string name = null, Dictionary<string, string> with = null, Dictionary<string, string> env = null, string condition = null, bool continueOnError = false, int? timeoutMinutes = null); IActionRunner WrapInRunner( Pipelines.ActionStep step, IExecutionContext jobContext, ActionRunStage stage = ActionRunStage.Main); } -
CreateRunStep implementation:
public Pipelines.ActionStep CreateRunStep(...) { var step = new Pipelines.ActionStep { Id = Guid.NewGuid(), Name = $"_dynamic_{Guid.NewGuid():N}", DisplayName = name ?? "Run script", Reference = new ScriptReference(), Condition = condition ?? "success()", ContinueOnError = CreateBoolToken(continueOnError), TimeoutInMinutes = CreateIntToken(timeoutMinutes) }; // Build Inputs mapping with script, shell, working-directory step.Inputs = CreateRunInputs(script, shell, workingDirectory); // Build Environment mapping if (env?.Count > 0) step.Environment = CreateEnvToken(env); return step; } -
CreateUsesStep implementation:
public Pipelines.ActionStep CreateUsesStep(string actionReference, ...) { var (name, ref_, path) = ParseActionReference(actionReference); var step = new Pipelines.ActionStep { Id = Guid.NewGuid(), Name = $"_dynamic_{Guid.NewGuid():N}", DisplayName = displayName ?? actionReference, Reference = new RepositoryPathReference { Name = name, // "actions/checkout" Ref = ref_, // "v4" Path = path, // null or "subdir" RepositoryType = "GitHub" }, Condition = condition ?? "success()" }; // Build with inputs if (with?.Count > 0) step.Inputs = CreateWithInputs(with); return step; } -
ParseActionReference helper:
actions/checkout@v4→ name=actions/checkout, ref=v4, path=nullactions/setup-node@v4→ name=actions/setup-node, ref=v4owner/repo/subdir@ref→ name=owner/repo, ref=ref, path=subdirdocker://alpine:latest→ ContainerRegistryReference instead
-
WrapInRunner: Create IActionRunner from ActionStep (copy pattern from JobExtension.cs):
public IActionRunner WrapInRunner(Pipelines.ActionStep step, IExecutionContext jobContext, ActionRunStage stage) { var runner = HostContext.CreateService<IActionRunner>(); runner.Action = step; runner.Stage = stage; runner.Condition = step.Condition; runner.ExecutionContext = jobContext.CreateChild( Guid.NewGuid(), step.DisplayName, step.Name ); return runner; }
Testing:
- Create run step → verify all properties
- Create uses step → verify reference parsing
- Test edge cases (missing shell, local actions, docker actions)
Estimated effort: Medium
Chunk 4: Step Manipulator (Queue operations)
Goal: Manipulate the job step queue (add, remove, move, track changes).
Files to create:
src/Runner.Worker/Dap/StepCommands/StepManipulator.cssrc/Runner.Worker/Dap/StepCommands/StepInfo.cssrc/Runner.Worker/Dap/StepCommands/StepChange.cs
Details:
-
StepInfo — Unified step representation:
public class StepInfo { public int Index { get; set; } public string Name { get; set; } public string Type { get; set; } // "run" or "uses" public string TypeDetail { get; set; } // action ref or script preview public StepStatus Status { get; set; } // Completed, Current, Pending public ChangeType? Change { get; set; } // Added, Modified, null public Pipelines.ActionStep Action { get; set; } public IStep Step { get; set; } } public enum StepStatus { Completed, Current, Pending } public enum ChangeType { Added, Modified, Removed, Moved } -
StepChange — Track modifications:
public class StepChange { public ChangeType Type { get; set; } public int OriginalIndex { get; set; } public StepInfo OriginalStep { get; set; } public StepInfo ModifiedStep { get; set; } } -
IStepManipulator interface:
public interface IStepManipulator : IRunnerService { // Initialize with job context void Initialize(IExecutionContext jobContext, int currentStepIndex); void UpdateCurrentIndex(int index); // Query IReadOnlyList<StepInfo> GetAllSteps(); StepInfo GetStep(int index); int GetPendingCount(); int GetFirstPendingIndex(); // Mutate int InsertStep(IStep step, StepPosition position); void RemoveStep(int index); void MoveStep(int fromIndex, StepPosition position); void EditStep(int index, Action<Pipelines.ActionStep> edit); // Change tracking IReadOnlyList<StepChange> GetChanges(); void RecordOriginalState(); // Call at session start } -
Implementation details:
- Maintain list of completed steps (from checkpoints/history)
- Access
jobContext.JobStepsqueue for pending steps - Track all modifications for export diff
- Validate indices before operations
-
Queue manipulation:
public int InsertStep(IStep step, StepPosition position) { // Convert queue to list var pending = _jobContext.JobSteps.ToList(); _jobContext.JobSteps.Clear(); // Calculate insertion index int insertAt = CalculateInsertIndex(position, pending.Count); // Insert pending.Insert(insertAt, step); // Re-queue foreach (var s in pending) _jobContext.JobSteps.Enqueue(s); // Track change _changes.Add(new StepChange { Type = ChangeType.Added, ... }); return _currentIndex + insertAt + 1; // Return 1-based index }
Testing:
- Insert at various positions
- Remove and verify queue state
- Move operations
- Change tracking accuracy
Estimated effort: Medium
Chunk 5: REPL Commands (run steps)
Goal: Implement steps list, steps add run, steps edit, steps remove, steps move.
Files to modify:
src/Runner.Worker/Dap/DapDebugSession.cs— AddHandleStepCommandAsync()
Files to create:
src/Runner.Worker/Dap/StepCommands/StepCommandHandler.cs
Details:
-
IStepCommandHandler interface:
public interface IStepCommandHandler : IRunnerService { Task<StepCommandResult> HandleAsync(StepCommand command, IExecutionContext context); } -
Command implementations:
List:
case ListCommand list: var steps = _manipulator.GetAllSteps(); var output = FormatStepList(steps, list.Verbose); return new StepCommandResult { Success = true, Result = steps, Message = output };Add run:
case AddRunCommand add: var actionStep = _factory.CreateRunStep( add.Script, add.Name, add.Shell, add.WorkingDirectory, add.Env, add.Condition, add.ContinueOnError, add.Timeout); var runner = _factory.WrapInRunner(actionStep, context); var index = _manipulator.InsertStep(runner, add.Position); return new StepCommandResult { Success = true, Message = $"Step added at position {index}", Result = new { index, step = GetStepInfo(runner) } };Edit:
case EditCommand edit: ValidatePendingIndex(edit.Index); _manipulator.EditStep(edit.Index, step => { if (edit.Script != null) UpdateScript(step, edit.Script); if (edit.Name != null) step.DisplayName = edit.Name; if (edit.Condition != null) step.Condition = edit.Condition; // ... other fields }); return new StepCommandResult { Success = true, Message = $"Step {edit.Index} updated" };Remove:
case RemoveCommand remove: ValidatePendingIndex(remove.Index); _manipulator.RemoveStep(remove.Index); return new StepCommandResult { Success = true, Message = $"Step {remove.Index} removed" };Move:
case MoveCommand move: ValidatePendingIndex(move.FromIndex); var newIndex = _manipulator.MoveStep(move.FromIndex, move.Position); return new StepCommandResult { Success = true, Message = $"Step moved to position {newIndex}" }; -
Integration in DapDebugSession:
private async Task<Response> HandleStepCommandAsync(string expression, IExecutionContext context) { try { var command = _commandParser.Parse(expression); var result = await _commandHandler.HandleAsync(command, context); if (result.Success) return CreateSuccessResponse(new EvaluateResponseBody { Result = result.Message, Type = "string" }); else return CreateErrorResponse($"{result.Error}: {result.Message}"); } catch (StepCommandException ex) { return CreateErrorResponse(ex.Message); } }
Testing:
- End-to-end: add step → list → verify
- Edit various properties
- Remove and verify indices shift
- Move operations
Estimated effort: Medium-large
Chunk 6: Action Download Integration (steps add uses)
Goal: Support steps add uses with full action download.
Files to modify:
src/Runner.Worker/Dap/StepCommands/StepCommandHandler.cs
Details:
-
Add uses command handling:
case AddUsesCommand add: // Create the step var actionStep = _factory.CreateUsesStep( add.Action, add.Name, add.With, add.Env, add.Condition, add.ContinueOnError, add.Timeout); // Download the action (this is the key difference from run steps) var actionManager = HostContext.GetService<IActionManager>(); var prepareResult = await actionManager.PrepareActionsAsync( context, new[] { actionStep } ); // Check for pre-steps (some actions have setup steps) if (prepareResult.PreStepTracker.TryGetValue(actionStep.Id, out var preStep)) { // Insert pre-step before main step var preRunner = _factory.WrapInRunner(preStep.Action, context, ActionRunStage.Pre); _manipulator.InsertStep(preRunner, add.Position); } // Wrap and insert main step var runner = _factory.WrapInRunner(actionStep, context); var index = _manipulator.InsertStep(runner, CalculateMainPosition(add.Position, hasPreStep)); // Handle post-steps (cleanup) // These go to PostJobSteps stack, handled by existing infrastructure return new StepCommandResult { Success = true, Message = $"Action '{add.Action}' added at position {index}", Result = new { index, step = GetStepInfo(runner) } }; -
Error handling:
- Action not found → clear error message with action name
- Network failure → suggest retry
- Invalid action reference format → parse error
-
Production note (for future):
// TODO: Before production release, add action restriction checks: // - Verify action is in organization's allowed list // - Check verified creator requirements // - Enforce enterprise policies // For now, allow all actions in prototype
Testing:
- Add common actions (checkout, setup-node)
- Test actions with pre/post steps
- Test local actions (./.github/actions/...)
- Test docker actions (docker://...)
- Error cases: invalid action, network failure
Estimated effort: Medium
Chunk 7: Export Command (steps export)
Goal: Generate YAML output for modified steps.
Files to modify:
src/Runner.Worker/Dap/StepCommands/StepCommandHandler.cs
Details:
-
Export command handling:
case ExportCommand export: var steps = _manipulator.GetAllSteps(); var changes = _manipulator.GetChanges(); IEnumerable<StepInfo> toExport; if (export.ChangesOnly) { toExport = steps.Where(s => s.Change != null); } else { toExport = steps; } var yaml = _serializer.ToYaml(toExport, export.WithComments); return new StepCommandResult { Success = true, Message = yaml, Result = new { yaml, totalSteps = steps.Count, addedCount = changes.Count(c => c.Type == ChangeType.Added), modifiedCount = changes.Count(c => c.Type == ChangeType.Modified) } }; -
YAML output format:
steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Node # ADDED uses: actions/setup-node@v4 with: node-version: '20' - name: Run Tests # MODIFIED run: | npm ci npm test shell: bash -
Change comments (when --with-comments):
# ADDEDfor new steps# MODIFIEDfor edited steps- Removed steps listed at bottom as comments (optional)
Testing:
- Export with no changes → valid YAML
- Export with additions → ADDED comments
- Export with modifications → MODIFIED comments
- Verify YAML is valid and can be pasted into workflow
Estimated effort: Small
Chunk 8: Output Format Flag for Browser Extension
Goal: Add --output flag support for programmatic access (replaces separate JSON API).
Files to modify:
src/Runner.Worker/Dap/StepCommands/StepCommandParser.cssrc/Runner.Worker/Dap/StepCommands/StepCommandHandler.cs
Details:
-
Add OutputFormat enum and property to base StepCommand:
public enum OutputFormat { Text, Json } public abstract class StepCommand { public OutputFormat Output { get; set; } = OutputFormat.Text; } -
Parse
--outputflag in command parser:// Recognize --output json, --output text, -o json, -o text, --output=json private OutputFormat ParseOutputFlag(List<string> tokens) { for (int i = 0; i < tokens.Count; i++) { var token = tokens[i].ToLower(); if (token == "--output" || token == "-o") { if (i + 1 < tokens.Count) { var format = tokens[i + 1].ToLower(); tokens.RemoveAt(i); tokens.RemoveAt(i); return format == "json" ? OutputFormat.Json : OutputFormat.Text; } } else if (token.StartsWith("--output=")) { var format = token.Substring("--output=".Length); tokens.RemoveAt(i); return format == "json" ? OutputFormat.Json : OutputFormat.Text; } } return OutputFormat.Text; } -
Format responses based on Output property in handler:
if (command.Output == OutputFormat.Json) { return new StepCommandResult { Success = true, Message = JsonConvert.SerializeObject(new { Success = true, Result = data }) }; } else { return new StepCommandResult { Success = true, Message = FormatAsText(data) }; } -
Browser extension sends REPL commands with
--output json:// Browser extension builds command strings like: // "steps list --output json" // "steps add run \"echo test\" --after 3 --output json"
Benefits over separate JSON API:
- Single code path for parsing all commands
- Easier debugging (UI sends same commands humans would type)
- Commands can be copy-pasted from UI to console for testing
- Less code to maintain
Testing:
- All commands with
--output jsonreturn valid JSON - All commands with
--output text(or default) return human-readable text - Short form
-o jsonworks correctly
Estimated effort: Small
Chunk 9: Browser Extension UI
Goal: Add step manipulation UI to the browser extension.
Files to modify:
browser-ext/content/content.jsbrowser-ext/content/content.cssbrowser-ext/background/background.js
Details:
-
Steps Panel in Debugger Pane:
<div class="dap-steps-panel"> <div class="dap-steps-header"> <span>Steps</span> <button class="dap-add-step-btn">+ Add</button> </div> <div class="dap-steps-list"> <!-- Steps rendered here --> </div> <div class="dap-steps-footer"> <button class="dap-export-btn">Export Changes</button> </div> </div> -
Step List Rendering:
function renderSteps(steps) { return steps.map(step => ` <div class="dap-step ${step.status}" data-index="${step.index}"> <span class="dap-step-status">${getStatusIcon(step.status)}</span> <span class="dap-step-index">${step.index}.</span> <span class="dap-step-name">${step.name}</span> ${step.change ? `<span class="dap-step-badge">[${step.change}]</span>` : ''} <span class="dap-step-type">${step.type}</span> ${step.status === 'pending' ? renderStepActions(step) : ''} </div> `).join(''); } -
Add Step Dialog:
function showAddStepDialog() { // Modal with: // - Type selector: run / uses // - For run: script textarea, shell dropdown // - For uses: action input with autocomplete // - Common: name, if condition, env vars // - Position: dropdown (after current, at end, at position) } -
Step Context Menu:
function showStepContextMenu(stepIndex, event) { // Edit, Move Up, Move Down, Delete } -
Export Modal:
function showExportModal(yaml) { // Code view with syntax highlighting // Copy to clipboard button } -
Send commands via REPL format with
--output json:async function addStep(type, options) { const cmd = buildStepCommand('step.add', { type, ...options }); const response = await sendEvaluate(cmd); // e.g., "steps add run \"echo test\" --output json" refreshStepList(); } async function loadSteps() { const response = await sendEvaluate('steps list --output json'); // Parse JSON response }
Testing:
- Manual testing in Chrome
- All UI operations work correctly
- Responsive layout
Estimated effort: Medium-large
File Summary
New Files
| File | Chunk | Purpose |
|---|---|---|
StepCommands/StepCommandParser.cs |
1 | Parse REPL and JSON commands |
StepCommands/StepCommandResult.cs |
1 | Standardized command responses |
StepCommands/StepSerializer.cs |
2 | ActionStep → YAML conversion |
StepCommands/StepFactory.cs |
3 | Create new ActionStep objects |
StepCommands/StepManipulator.cs |
4 | Queue operations & change tracking |
StepCommands/StepInfo.cs |
4 | Step info data structure |
StepCommands/StepChange.cs |
4 | Change tracking data structure |
StepCommands/StepCommandHandler.cs |
5 | Command execution logic |
Modified Files
| File | Chunk | Changes |
|---|---|---|
DapDebugSession.cs |
1, 5 | Add command dispatch, wire up services |
StepCommandParser.cs |
8 | Add --output flag parsing |
StepCommandHandler.cs |
8 | Format responses based on OutputFormat |
content.js |
9 | Steps panel, dialogs, export modal, build REPL commands |
content.css |
9 | Styling for new UI elements |
background.js |
9 | Helper functions if needed |
Command API Full Reference
Output Format Flag
All commands support the --output flag:
steps <command> ... --output text # Human-readable output (default)
steps <command> ... --output json # JSON output for programmatic use
steps <command> ... -o json # Short form
steps <command> ... --output=json # Equals form
The browser extension uses --output json for all commands to receive structured responses.
steps list
steps list [--verbose] [--output text|json]
Show all steps with their indices, status, and modification state.
Output format:
Steps:
✓ 1. Checkout uses actions/checkout@v4
✓ 2. Setup Node uses actions/setup-node@v4
▶ 3. Install deps run npm ci
4. Run tests [MODIFIED] run npm test
5. Build [ADDED] run npm run build
6. Deploy run ./deploy.sh
Legend: ✓ = completed, ▶ = current/paused, [ADDED] = new, [MODIFIED] = edited
JSON output (steps list --output json):
{
"Success": true,
"Result": [
{"index": 1, "name": "Checkout", "type": "uses", "typeDetail": "actions/checkout@v4", "status": "completed"},
{"index": 2, "name": "Setup Node", "type": "uses", "typeDetail": "actions/setup-node@v4", "status": "completed"},
{"index": 3, "name": "Install deps", "type": "run", "typeDetail": "npm ci", "status": "current"},
{"index": 4, "name": "Run tests", "type": "run", "typeDetail": "npm test", "status": "pending", "change": "MODIFIED"},
{"index": 5, "name": "Build", "type": "run", "typeDetail": "npm run build", "status": "pending", "change": "ADDED"}
]
}
steps add
Run step:
steps add run "<script>" [options]
Options:
--name "<name>" Display name
--shell <shell> Shell (bash, sh, pwsh, etc.)
--working-directory <path> Working directory
--if "<condition>" Condition expression
--env KEY=value Environment variable (repeatable)
--continue-on-error Don't fail job on step failure
--timeout <minutes> Step timeout
--at <index> Insert at position
--after <index> Insert after step
--before <index> Insert before step
--first Insert at first pending position
--last Insert at end (default)
--output text|json Response format (default: text)
Uses step:
steps add uses <action> [options]
Options:
--name "<name>" Display name
--with key=value Input parameter (repeatable)
--if "<condition>" Condition expression
--env KEY=value Environment variable (repeatable)
--continue-on-error Don't fail job on step failure
--timeout <minutes> Step timeout
[position options same as run]
--output text|json Response format (default: text)
Examples:
# Human usage (text output)
steps add run "npm test" --name "Run Tests" --after 3
# Browser extension (JSON output)
steps add uses actions/setup-node@v4 --with node-version=20 --output json
JSON response (--output json):
{
"Success": true,
"Message": "Step added at position 4",
"Result": {"index": 4, "name": "Setup Node", "type": "uses", "typeDetail": "actions/setup-node@v4", "status": "pending", "change": "ADDED"}
}
Position options:
--at 3— Insert at position 3--after 2— Insert after step 2--before 4— Insert before step 4--first— Insert at first pending position--last— Insert at end (default if omitted)
steps edit
steps edit <index> [modifications]
Modifications:
--name "<name>" Change display name
--script "<script>" Change script (run only)
--action "<action>" Change action (uses only)
--shell <shell> Change shell (run only)
--working-directory <path> Change working directory
--if "<condition>" Change condition
--with key=value Set/update input (uses only)
--env KEY=value Set/update env var
--remove-with <key> Remove input
--remove-env <KEY> Remove env var
--continue-on-error Enable continue-on-error
--no-continue-on-error Disable continue-on-error
--timeout <minutes> Change timeout
--output text|json Response format (default: text)
Examples:
# Human usage
steps edit 4 --script "npm run test:ci" --name "CI Tests"
# Browser extension
steps edit 4 --script "npm run test:ci" --output json
JSON response (--output json):
{
"Success": true,
"Message": "Step 4 updated",
"Result": {"index": 4, "name": "CI Tests", "type": "run", "typeDetail": "npm run test:ci", "status": "pending", "change": "MODIFIED"}
}
steps remove
steps remove <index> [--output text|json]
Examples:
steps remove 5
steps remove 5 --output json
JSON response (--output json):
{"Success": true, "Message": "Step 5 removed"}
steps move
steps move <from> <position> [--output text|json]
Position (one required):
--to <index> Move to position
--after <index> Move after step
--before <index> Move before step
--first Move to first pending position
--last Move to end
Examples:
steps move 5 --after 2
steps move 5 --after 2 --output json
JSON response (--output json):
{"Success": true, "Message": "Step moved to position 3"}
steps export
steps export [--changes-only] [--with-comments] [--output text|json]
Examples:
steps export --with-comments
steps export --changes-only --output json
JSON response (--output json):
{
"Success": true,
"Result": {
"yaml": "steps:\n - name: Checkout\n uses: actions/checkout@v4\n...",
"totalSteps": 5,
"addedCount": 1,
"modifiedCount": 1
}
}
!step edit [modifications]
Modifications: --name "" Change display name --script "<script>" Change script (run only) --action "" Change action (uses only) --shell Change shell (run only) --working-directory Change working directory --if "" Change condition --with key=value Set/update input (uses only) --env KEY=value Set/update env var --remove-with Remove input --remove-env Remove env var --continue-on-error Enable continue-on-error --no-continue-on-error Disable continue-on-error --timeout Change timeout
**JSON:**
```json
{
"cmd": "step.edit",
"index": 4,
"script": "npm run test:ci",
"name": "CI Tests",
"with": {"node-version": "22"},
"removeWith": ["cache"],
"env": {"CI": "true"},
"removeEnv": ["DEBUG"]
}
!step remove
!step remove <index>
JSON:
{"cmd": "step.remove", "index": 5}
!step move
!step move <from> <position>
Position (one required):
--to <index> Move to position
--after <index> Move after step
--before <index> Move before step
--first Move to first pending position
--last Move to end
JSON:
{"cmd": "step.move", "from": 5, "position": {"after": 2}}
!step export
!step export [--changes-only] [--with-comments]
JSON:
{"cmd": "step.export", "changesOnly": false, "withComments": true}
Validation Rules
- Index bounds: Must be 1 ≤ index ≤ total_steps
- Completed steps: Cannot edit, remove, or move completed steps
- Current step: Cannot remove or move the currently executing step (can edit for next run if step-back)
- Position conflicts:
--at,--after,--before,--first,--lastare mutually exclusive - Type-specific options:
--script,--shell,--working-directoryonly for run;--action,--withonly for uses - Required values:
--at,--after,--before,--torequire an index value
Error Codes
| Code | Description |
|---|---|
INVALID_INDEX |
Index out of range or refers to completed/current step |
INVALID_COMMAND |
Unknown command |
INVALID_OPTION |
Unknown or conflicting options |
INVALID_TYPE |
Invalid step type (not "run" or "uses") |
ACTION_DOWNLOAD_FAILED |
Failed to download action for uses step |
PARSE_ERROR |
Failed to parse command/JSON |
Future Commands (Reserved)
These command names are reserved for future implementation:
steps duplicate <index> # Clone a step
steps enable <index> # Re-enable a disabled step
steps disable <index> # Skip step without removing
steps inspect <index> # Show detailed step info
steps reset <index> # Revert modifications
steps import # Add steps from YAML
Notes for Production
-
Action Restrictions: Before production, must integrate with organization/enterprise action policies (allowed lists, verified creators, etc.)
-
Security: Dynamic step execution has security implications - ensure proper sandboxing and audit logging
-
Persistence: Consider whether modifications should persist across step-back operations
-
Undo: Consider adding
!step undofor reverting last operation