AgentSkillsCN

Add Command

添加命令

SKILL.md

Add Command Skill

Guide users through adding a new command handler to the manny RuneLite plugin.

When to Use

  • User requests to add a new command/feature to the plugin
  • Creating new automation capabilities
  • Extending the plugin's command set

Workflow

1. Discovery Phase

Ask the user what the command should do:

code
- What should the command be called? (e.g., "SMELT_BARS", "AGILITY_COURSE")
- What action should it perform?
- What arguments does it need? (e.g., "item_name quantity", "course_name")
- Should it loop or run once?

2. Check for Existing Commands

Before creating a new command, check if similar functionality exists:

code
list_available_commands(search="<keyword>")
get_command_examples(command="SIMILAR_COMMAND")

3. Get manny Guidelines

Load the development guidelines to understand command patterns:

code
get_manny_guidelines(mode="condensed")

Focus on:

  • Command handler structure
  • Available wrappers (interactionSystem, gameEngine, etc.)
  • Thread safety rules
  • Common pitfalls

4. Generate Command Template

Use the template generator to create skeleton code:

code
generate_command_template(
    command_name="<COMMAND_NAME>",
    description="<what it does>",
    args_format="<arg1> <arg2>",  # e.g., "item_name quantity"
    has_args=true/false,
    has_loop=true/false
)

This generates:

  • Switch case entry
  • Handler method skeleton
  • Proper logging tags
  • ResponseWriter calls
  • Interrupt checks (if looping)

5. Find Similar Implementations

Look for precedents to learn from existing code:

code
find_similar_fix(problem="<what the command does>")

Examples:

  • "NPC interaction" → shows interactionSystem.interactWithNPC() usage
  • "banking workflow" → shows BANK_OPEN/DEPOSIT/WITHDRAW patterns
  • "inventory checking" → shows gameEngine.hasItems() usage

6. Implementation

Guide user through implementation:

a. Backup files

code
backup_files(file_paths=["manny_src/utility/PlayerHelpers.java"])

b. Add switch case

Tell user where to add (based on get_section output):

code
get_section(section="list")  # Find the right section

Then add switch case around line XXXX (find via grep):

java
case "YOUR_COMMAND":
    return handleYourCommand(args);

c. Add handler method

Add the handler method implementation using wrappers:

java
private boolean handleYourCommand(String args) {
    log.info("[YOUR_COMMAND] Starting...");

    // Parse arguments
    String[] parts = args.split("\\s+");

    try {
        // Use wrappers instead of manual code:
        // - interactionSystem.interactWithNPC(name, action)
        // - interactionSystem.interactWithGameObject(name, action, radius)
        // - gameEngine.hasItems(itemId1, itemId2, ...)
        // - clickWidget(widgetId)

        responseWriter.writeSuccess("YOUR_COMMAND", "Completed");
        return true;

    } catch (Exception e) {
        log.error("[YOUR_COMMAND] Error", e);
        responseWriter.writeFailure("YOUR_COMMAND", e);
        return false;
    }
}

7. Validation

a. Check for anti-patterns

code
check_anti_patterns(file_path="manny_src/utility/PlayerHelpers.java")

Fix any errors before proceeding.

b. Compile check

code
validate_code_change(modified_files=["manny_src/utility/PlayerHelpers.java"])

8. Deployment

code
deploy_code_change(restart_after=True)

9. Testing

a. Start RuneLite (if not running)

code
start_runelite()

b. Send test command

code
send_command("YOUR_COMMAND <test args>")

c. Observe results

code
get_logs(level="INFO", since_seconds=30, grep="YOUR_COMMAND")
get_command_response()
get_game_state()  # Check side effects

d. If issues occur

code
rollback_code_change()

Then iterate on the fix.

Common Patterns

Simple NPC Interaction Command

java
private boolean handleTalkToNpc(String args) {
    log.info("[TALK_TO_NPC] Target: {}", args);

    try {
        boolean success = interactionSystem.interactWithNPC(args, "Talk-to");

        if (success) {
            responseWriter.writeSuccess("TALK_TO_NPC", "Talked to " + args);
            return true;
        } else {
            responseWriter.writeFailure("TALK_TO_NPC", "NPC not found: " + args);
            return false;
        }
    } catch (Exception e) {
        log.error("[TALK_TO_NPC] Error", e);
        responseWriter.writeFailure("TALK_TO_NPC", e);
        return false;
    }
}

Looping Command with Interrupt

java
private boolean handleFishLoop(String args) {
    log.info("[FISH_LOOP] Starting fishing loop");

    try {
        while (!shouldInterrupt) {
            // Do work
            interactionSystem.interactWithNPC("Fishing spot", "Net");

            // Wait
            Thread.sleep(1000);

            // Check inventory
            if (!gameEngine.hasInventorySpace(1)) {
                log.info("[FISH_LOOP] Inventory full, banking");
                handleBankOpen("");
                handleBankDepositAll("");
                handleBankClose("");
            }
        }

        responseWriter.writeSuccess("FISH_LOOP", "Stopped");
        return true;

    } catch (InterruptedException e) {
        log.info("[FISH_LOOP] Interrupted");
        responseWriter.writeFailure("FISH_LOOP", "Interrupted");
        return false;
    } catch (Exception e) {
        log.error("[FISH_LOOP] Error", e);
        responseWriter.writeFailure("FISH_LOOP", e);
        return false;
    }
}

Command with Arguments

java
private boolean handleSmelt Bars(String args) {
    // Parse: "SMELT_BARS bronze_bar 28"
    String[] parts = args.split("\\s+");
    if (parts.length < 2) {
        responseWriter.writeFailure("SMELT_BARS", "Usage: SMELT_BARS <bar_type> <quantity>");
        return false;
    }

    String barType = parts[0];
    int quantity = Integer.parseInt(parts[1]);

    log.info("[SMELT_BARS] Type: {}, Quantity: {}", barType, quantity);

    try {
        // Implementation...
        responseWriter.writeSuccess("SMELT_BARS", "Smelted " + quantity + " " + barType);
        return true;
    } catch (Exception e) {
        log.error("[SMELT_BARS] Error", e);
        responseWriter.writeFailure("SMELT_BARS", e);
        return false;
    }
}

Anti-Patterns to Avoid

Don't use smartClick() for NPCs → Use interactionSystem.interactWithNPC()

Don't manually fetch widgets with CountDownLatch → Use ClientThreadHelper.readFromClient()

Don't write manual retry loops → Wrappers have retries built-in

Don't use F-keys for tab switching → Use widget IDs (F-keys are user-configurable)

Don't forget ResponseWriter → Always call writeSuccess/writeFailure

Don't forget interrupt checks in loops → Check shouldInterrupt regularly

Success Criteria

  • Command compiles without errors
  • No anti-pattern violations
  • Command appears in list_available_commands()
  • Test execution succeeds
  • Logs show proper [TAG] format
  • ResponseWriter called correctly
  • Code follows existing patterns

Tips

  • Start simple: Get basic version working first, then add features
  • Use wrappers: Don't reinvent NPC/GameObject interaction
  • Check precedents: Find similar commands and learn from them
  • Test incrementally: Deploy and test after each significant change
  • Use interrupts: All loops should check shouldInterrupt
  • Log liberally: Use [COMMAND_NAME] prefix for easy filtering