@Override
  public boolean execute(Plugin plugin, CommandSender sender, String[] args) {

    String menuName = args[0];
    String itemLabel = SMSUtil.unEscape(args[1]);

    if (itemLabel.matches("@[0-9]+")) {
      // backwards compatibility - numeric indices should be prefixed with a '@'
      // but we'll allow raw numbers to be used
      itemLabel = itemLabel.substring(1);
    }

    try {
      SMSMenu menu = getMenu(sender, menuName);
      menu.ensureAllowedToModify(sender);
      int pos = menu.indexOfItem(itemLabel);
      SMSMenuItem menuItem = menu.getItemAt(pos);
      menu.removeItem(pos);
      menu.notifyObservers(new RemoveItemAction(sender, menuItem));
      MiscUtil.statusMessage(
          sender, "Menu entry &f#" + itemLabel + "&- removed from &e" + menu.getName());
    } catch (IndexOutOfBoundsException e) {
      throw new SMSException("Item index " + itemLabel + " out of range");
    } catch (IllegalArgumentException e) {
      throw new SMSException(e.getMessage());
    }

    return true;
  }
  @Override
  public boolean execute(ScrollingMenuSign plugin, Player player, String[] args)
      throws SMSException {

    String menuName = args[0];

    String sep = SMSConfig.getConfig().getString("sms.menuitem_separator", "|");
    List<String> items;
    if (args[1].contains(sep)) {
      items = Arrays.asList(combine(args, 1).split(Pattern.quote(sep)));
      String[] usage = getUsage();
      MiscUtil.statusMessage(player, "&6NOTE: preferred syntax is now &f" + usage[0]);
      MiscUtil.statusMessage(
          player, " &6(label/command/message can be quoted if they contain whitespace)");
    } else {
      items = new ArrayList<String>();
      for (int i = 1; i < args.length; i++) {
        items.add(args[i]);
      }
    }

    SMSMenu menu = plugin.getHandler().getMenu(menuName);

    if (items.size() < 2 && menu.getDefaultCommand().isEmpty()) {
      throw new SMSException("Missing command and feedback message");
    }

    String label = MiscUtil.parseColourSpec(player, items.get(0));
    String cmd = items.size() >= 2 ? items.get(1) : "";
    String msg = items.size() >= 3 ? items.get(2) : "";

    if (player != null && !new CommandParser().verifyCreationPerms(player, cmd)) {
      throw new SMSException("You do not have permission to add that kind of command.");
    }

    menu.addItem(label, cmd, msg);
    menu.notifyObservers(SMSMenuAction.REPAINT);

    MiscUtil.statusMessage(player, "Menu entry &f" + label + "&- added to: &e" + menuName);

    return true;
  }
  @Override
  public boolean execute(ScrollingMenuSign plugin, Player player, String[] args)
      throws SMSException {
    SMSView view = null;
    SMSMenu menu = plugin.getHandler().getMenu(args[0]);

    if (args.length == 2 && args[1].equalsIgnoreCase("-spout")) { // spout view
      if (plugin.isSpoutEnabled()) view = SMSSpoutView.addSpoutViewToMenu(menu);
      else throw new SMSException("Server is not Spout-enabled");
    } else if (args.length == 3 && args[1].equalsIgnoreCase("-sign")) { // sign view
      Location loc = MiscUtil.parseLocation(args[2], player);
      view = SMSSignView.addSignToMenu(menu, loc);
    } else if (args.length == 3 && args[1].equalsIgnoreCase("-redstone")) { // redstone view
      Location loc = MiscUtil.parseLocation(args[2], player);
      view = SMSRedstoneView.addRedstoneViewToMenu(menu, loc);
    } else if (args.length == 2
        && (args[1].equalsIgnoreCase("-sign") || args[1].equalsIgnoreCase("-redstone"))) {
      // create a new view interactively
      notFromConsole(player);
      String type = args[1].substring(1);
      MiscUtil.statusMessage(
          player,
          "Left-click a block to add it as a &9"
              + type
              + "&- view on menu &e"
              + menu.getName()
              + "&-.");
      MiscUtil.statusMessage(player, "Right-click anywhere to cancel.");
      plugin.expecter.expectingResponse(player, new ExpectViewCreation(menu, args[1]));
      return true;
    } else if (args.length == 3 && args[1].equalsIgnoreCase("-map")) { // map view
      try {
        short mapId = Short.parseShort(args[2]);
        view = SMSMapView.addMapToMenu(menu, mapId);
      } catch (NumberFormatException e) {
        throw new SMSException(e.getMessage());
      }
    } else if (args.length > 1) {
      MiscUtil.errorMessage(player, "Unknown view type: " + args[1]);
      return false;
    }

    if (view == null) {
      // see if we can get a view from what the player is looking at or holding
      notFromConsole(player);
      if (player.getItemInHand().getType() == Material.MAP) { // map view
        PermissionsUtils.requirePerms(player, "scrollingmenusign.use.map");
        short mapId = player.getItemInHand().getDurability();
        view = SMSMapView.addMapToMenu(menu, mapId);
      } else {
        try {
          Block b = player.getTargetBlock(null, 3); // sign view ?
          if (b.getType() == Material.WALL_SIGN || b.getType() == Material.SIGN_POST) {
            view = SMSSignView.addSignToMenu(menu, b.getLocation());
          }
        } catch (IllegalStateException e) {
          // ignore
        }
      }
    }

    if (view != null) {
      MiscUtil.statusMessage(
          player,
          String.format(
              "Added &9%s&- view &e%s&- to menu &e%s&-.",
              view.getType(), view.getName(), menu.getName()));
    } else {
      throw new SMSException("Found nothing suitable to add as a menu view");
    }

    return true;
  }