useCommands

React hooks for working with the Lokus command system. These hooks provide real-time updates when commands are registered, unregistered, or cleared.

Import

import {
  useCommands,
  useCommandExecute,
  useAllCommands,
  useCommandsByCategory,
  useCommandExists
} from '@lokus/plugin-sdk';

Hooks

useCommands()

Returns an array of commands that are visible in the command palette.

Returns: Command[]

Updates when:

  • Command is registered
  • Command is unregistered
  • Commands are cleared

Example:

import { useCommands } from '@lokus/plugin-sdk';
 
function CommandPalette() {
  const commands = useCommands();
 
  return (
    <div>
      <h3>Available Commands ({commands.length})</h3>
      <ul>
        {commands.map(cmd => (
          <li key={cmd.id}>
            {cmd.icon && <span className="icon">{cmd.icon}</span>}
            <strong>{cmd.title}</strong>
            {cmd.description && <p>{cmd.description}</p>}
            {cmd.category && <span className="badge">{cmd.category}</span>}
          </li>
        ))}
      </ul>
    </div>
  );
}

useCommandExecute()

Returns a stable callback function to execute commands by ID. The function accepts the command ID and any arguments to pass to the command handler.

Returns: (commandId: string, ...args: any[]) => Promise\<any\>

Parameters:

NameTypeDescription
commandIdstringThe ID of the command to execute
…argsany[]Arguments to pass to command handler

Returns: Promise that resolves with the command’s return value

Example:

import { useCommandExecute } from '@lokus/plugin-sdk';
 
function QuickActions() {
  const execute = useCommandExecute();
 
  const handleNewNote = async () => {
    try {
      await execute('file.new');
      console.log('New note created');
    } catch (error) {
      console.error('Failed to create note:', error);
    }
  };
 
  const handleSave = async () => {
    await execute('file.save');
  };
 
  const handleCustomCommand = async () => {
    // Pass arguments to command
    const result = await execute('myPlugin.process', 'arg1', 'arg2');
    console.log('Result:', result);
  };
 
  return (
    <div className="quick-actions">
      <button onClick={handleNewNote}>New Note</button>
      <button onClick={handleSave}>Save</button>
      <button onClick={handleCustomCommand}>Process</button>
    </div>
  );
}

Error Handling:

import { useCommandExecute } from '@lokus/plugin-sdk';
import { useState } from 'react';
 
function SafeCommandButton({ commandId, label }) {
  const execute = useCommandExecute();
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(false);
 
  const handleClick = async () => {
    setLoading(true);
    setError(null);
 
    try {
      await execute(commandId);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };
 
  return (
    <div>
      <button onClick={handleClick} disabled={loading}>
        {loading ? 'Running...' : label}
      </button>
      {error && <p className="error">{error}</p>}
    </div>
  );
}

useAllCommands()

Returns ALL registered commands, including those hidden from the command palette (showInPalette: false).

Returns: Command[]

Updates when:

  • Command is registered
  • Command is unregistered
  • Commands are cleared

Use cases:

  • Admin/debug panels
  • Plugin management UIs
  • Command discovery tools
  • Testing interfaces

Example:

import { useAllCommands } from '@lokus/plugin-sdk';
 
function CommandDebugger() {
  const allCommands = useAllCommands();
 
  const paletteCommands = allCommands.filter(cmd => cmd.showInPalette !== false);
  const hiddenCommands = allCommands.filter(cmd => cmd.showInPalette === false);
 
  return (
    <div>
      <h3>All Commands ({allCommands.length})</h3>
 
      <section>
        <h4>Palette Commands ({paletteCommands.length})</h4>
        <ul>
          {paletteCommands.map(cmd => (
            <li key={cmd.id}>{cmd.title}</li>
          ))}
        </ul>
      </section>
 
      <section>
        <h4>Hidden Commands ({hiddenCommands.length})</h4>
        <ul>
          {hiddenCommands.map(cmd => (
            <li key={cmd.id}>{cmd.id}</li>
          ))}
        </ul>
      </section>
    </div>
  );
}

useCommandsByCategory(category)

Filter commands by category. Returns only commands that match the specified category and are visible in the palette.

Parameters:

NameTypeDescription
categorystringCategory name to filter by

Returns: Command[]

Updates when:

  • Command is registered
  • Command is unregistered
  • Commands are cleared
  • Category parameter changes

Example:

import { useCommandsByCategory } from '@lokus/plugin-sdk';
 
function CategoryView({ category }) {
  const commands = useCommandsByCategory(category);
 
  return (
    <div>
      <h3>{category} Commands ({commands.length})</h3>
      <ul>
        {commands.map(cmd => (
          <li key={cmd.id}>
            <strong>{cmd.title}</strong>
            <p>{cmd.description}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}
 
// Usage
function CommandsByCategory() {
  return (
    <div>
      <CategoryView category="editor" />
      <CategoryView category="file" />
      <CategoryView category="git" />
    </div>
  );
}

Tabbed Interface Example:

import { useCommandsByCategory } from '@lokus/plugin-sdk';
import { useState } from 'react';
 
function TabbedCommands() {
  const [activeTab, setActiveTab] = useState('editor');
 
  const editorCommands = useCommandsByCategory('editor');
  const fileCommands = useCommandsByCategory('file');
  const gitCommands = useCommandsByCategory('git');
 
  const tabs = [
    { id: 'editor', label: 'Editor', commands: editorCommands },
    { id: 'file', label: 'File', commands: fileCommands },
    { id: 'git', label: 'Git', commands: gitCommands },
  ];
 
  const activeCommands = tabs.find(t => t.id === activeTab)?.commands || [];
 
  return (
    <div>
      <div className="tabs">
        {tabs.map(tab => (
          <button
            key={tab.id}
            onClick={() => setActiveTab(tab.id)}
            className={activeTab === tab.id ? 'active' : ''}
          >
            {tab.label} ({tab.commands.length})
          </button>
        ))}
      </div>
 
      <ul>
        {activeCommands.map(cmd => (
          <li key={cmd.id}>{cmd.title}</li>
        ))}
      </ul>
    </div>
  );
}

useCommandExists(commandId)

Check if a command is currently registered. Useful for conditional rendering based on plugin availability.

Parameters:

NameTypeDescription
commandIdstringCommand ID to check

Returns: boolean - true if command exists, false otherwise

Updates when:

  • Command is registered
  • Command is unregistered
  • Commands are cleared
  • commandId parameter changes

Example:

import { useCommandExists, useCommandExecute } from '@lokus/plugin-sdk';
 
function GitFeature() {
  const hasGitCommit = useCommandExists('git.commit');
  const hasGitPush = useCommandExists('git.push');
  const execute = useCommandExecute();
 
  if (!hasGitCommit) {
    return (
      <div className="warning">
        Git plugin not available. Please install the Git plugin to use this feature.
      </div>
    );
  }
 
  return (
    <div>
      <button onClick={() => execute('git.commit')}>
        Commit Changes
      </button>
      {hasGitPush && (
        <button onClick={() => execute('git.push')}>
          Push to Remote
        </button>
      )}
    </div>
  );
}

Graceful Degradation:

import { useCommandExists } from '@lokus/plugin-sdk';
 
function FeatureDetection() {
  const hasAdvancedFeature = useCommandExists('premium.advancedFeature');
 
  return (
    <div>
      <h3>Available Features</h3>
      <ul>
        <li>Basic Editing</li>
        <li>File Management</li>
        {hasAdvancedFeature && (
          <li>Advanced Analytics</li>
        )}
      </ul>
    </div>
  );
}

Command Interface

interface Command {
  id: string;              // Unique command identifier
  title: string;           // Display name in palette
  handler: Function;       // Function to execute
  category?: string;       // Command category
  description?: string;    // Help text
  icon?: string;          // Icon name or emoji
  showInPalette?: boolean; // Show in command palette (default: true)
  pluginId?: string;      // Plugin that registered this command
}

Common Patterns

Pattern 1: Search and Filter Commands

import { useCommands } from '@lokus/plugin-sdk';
import { useState, useMemo } from 'react';
 
function SearchableCommands() {
  const commands = useCommands();
  const [search, setSearch] = useState('');
 
  const filtered = useMemo(() => {
    const term = search.toLowerCase();
    return commands.filter(cmd =>
      cmd.title.toLowerCase().includes(term) ||
      cmd.description?.toLowerCase().includes(term)
    );
  }, [commands, search]);
 
  return (
    <div>
      <input
        type="text"
        placeholder="Search commands..."
        value={search}
        onChange={(e) => setSearch(e.target.value)}
      />
      <p>{filtered.length} results</p>
      <ul>
        {filtered.map(cmd => (
          <li key={cmd.id}>{cmd.title}</li>
        ))}
      </ul>
    </div>
  );
}

Pattern 2: Command Keyboard Shortcuts

import { useCommandExecute } from '@lokus/plugin-sdk';
import { useEffect } from 'react';
 
function KeyboardShortcuts() {
  const execute = useCommandExecute();
 
  useEffect(() => {
    const handleKeyDown = (e) => {
      // Ctrl/Cmd + S to save
      if ((e.ctrlKey || e.metaKey) && e.key === 's') {
        e.preventDefault();
        execute('file.save');
      }
 
      // Ctrl/Cmd + N to create new
      if ((e.ctrlKey || e.metaKey) && e.key === 'n') {
        e.preventDefault();
        execute('file.new');
      }
    };
 
    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
  }, [execute]);
 
  return <div>Keyboard shortcuts active</div>;
}

Pattern 3: Recent Commands List

import { useCommandExecute, useAllCommands } from '@lokus/plugin-sdk';
import { useState, useCallback } from 'react';
 
function RecentCommands() {
  const execute = useCommandExecute();
  const allCommands = useAllCommands();
  const [recent, setRecent] = useState([]);
 
  const executeAndTrack = useCallback(async (commandId) => {
    await execute(commandId);
 
    setRecent(prev => {
      const filtered = prev.filter(id => id !== commandId);
      return [commandId, ...filtered].slice(0, 5); // Keep last 5
    });
  }, [execute]);
 
  const recentCommands = recent
    .map(id => allCommands.find(cmd => cmd.id === id))
    .filter(Boolean);
 
  return (
    <div>
      <h3>Recent Commands</h3>
      <ul>
        {recentCommands.map(cmd => (
          <li key={cmd.id}>
            <button onClick={() => executeAndTrack(cmd.id)}>
              {cmd.title}
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

Pattern 4: Command Groups

import { useAllCommands } from '@lokus/plugin-sdk';
import { useMemo } from 'react';
 
function GroupedCommands() {
  const commands = useAllCommands();
 
  const grouped = useMemo(() => {
    const groups = {};
    commands.forEach(cmd => {
      const category = cmd.category || 'Other';
      if (!groups[category]) {
        groups[category] = [];
      }
      groups[category].push(cmd);
    });
    return groups;
  }, [commands]);
 
  return (
    <div>
      {Object.entries(grouped).map(([category, cmds]) => (
        <section key={category}>
          <h3>{category} ({cmds.length})</h3>
          <ul>
            {cmds.map(cmd => (
              <li key={cmd.id}>{cmd.title}</li>
            ))}
          </ul>
        </section>
      ))}
    </div>
  );
}

Pattern 5: Command Availability Indicator

import { useCommandExists } from '@lokus/plugin-sdk';
 
function PluginStatus() {
  const hasGit = useCommandExists('git.status');
  const hasLinter = useCommandExists('lint.check');
  const hasFormatter = useCommandExists('format.document');
 
  return (
    <div className="plugin-status">
      <h3>Available Features</h3>
      <ul>
        <li className={hasGit ? 'available' : 'unavailable'}>
          Git Integration {hasGit ? '✓' : '✗'}
        </li>
        <li className={hasLinter ? 'available' : 'unavailable'}>
          Code Linting {hasLinter ? '✓' : '✗'}
        </li>
        <li className={hasFormatter ? 'available' : 'unavailable'}>
          Code Formatting {hasFormatter ? '✓' : '✗'}
        </li>
      </ul>
    </div>
  );
}

Best Practices

1. Use Specific Hooks for Better Performance

// Good - only checks one command
const exists = useCommandExists('myPlugin.command');
 
// Less efficient - loads all commands
const commands = useAllCommands();
const exists = commands.some(cmd => cmd.id === 'myPlugin.command');

2. Handle Async Command Execution

Always handle errors and loading states when executing commands:

const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
 
const handleCommand = async () => {
  setLoading(true);
  setError(null);
  try {
    await execute('myCommand');
  } catch (err) {
    setError(err.message);
  } finally {
    setLoading(false);
  }
};

3. Memoize Filtered Lists

Use useMemo for expensive filtering operations:

const filtered = useMemo(() => {
  return commands.filter(cmd => /* expensive filter */);
}, [commands, dependencies]);

4. Check Command Existence Before Execution

Prevent errors by checking if optional commands exist:

const hasCommand = useCommandExists('optional.feature');
 
const handleClick = () => {
  if (hasCommand) {
    execute('optional.feature');
  } else {
    showMessage('Feature not available');
  }
};

5. Use Categories for Organization

Always set categories when registering commands:

// In your plugin activation
lokus.commands.register({
  id: 'myPlugin.action',
  title: 'My Action',
  category: 'myPlugin', // Makes filtering easier
  handler: () => { /* ... */ }
});