AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Vicinae Extensions

skill-brpaz-agent-skills-vicinae-extensions · by brpaz

Build Vicinae launcher extensions with React/TypeScript, @vicinae/api, commands, and native UI components.

No reviews yet
0 installs
28 views
0.0% view→install

Install

$ agentstack add skill-brpaz-agent-skills-vicinae-extensions

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-brpaz-agent-skills-vicinae-extensions)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
4mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Vicinae Extensions? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Vicinae Extensions - React/TypeScript Extension Development

Use this skill when building extensions for Vicinae launcher, a high-performance native Linux launcher with Raycast compatibility. This covers the complete extension development workflow using the @vicinae/api TypeScript SDK.

When to Use

  • Creating a new Vicinae extension with React/TypeScript
  • Implementing List, Grid, Form, or Detail view commands
  • Integrating with Vicinae APIs (clipboard, notifications, preferences, storage)
  • Porting an existing Raycast extension to Vicinae

What is Vicinae?

Vicinae (pronounced "vee-CHEE-nay") is a keyboard-driven command launcher for Linux that integrates with:

  • Native OS features (window management, clipboard)
  • React/TypeScript extension system
  • Raycast extension ecosystem (mostly compatible)
  • Global extension store

Architecture: Extensions run in Node.js and render through a custom React reconciler to native Qt Widgets (no browser, no Electron, no HTML/CSS).

Extension File Structure

my-extension/
├── package.json           # Extension metadata & dependencies
├── tsconfig.json          # TypeScript configuration
├── src/
│   ├── index.tsx         # Main command (default export)
│   ├── other-command.tsx # Additional commands
│   └── lib/
│       └── utils.ts      # Shared utilities
├── assets/               # Icons, images (optional)
│   └── icon.png
└── README.md            # Extension documentation

package.json Structure

{
  "name": "my-extension",
  "version": "1.0.0",
  "title": "My Extension",
  "description": "Brief description of what it does",
  "author": "Your Name",
  "license": "MIT",
  
  "main": "src/index.tsx",
  
  "scripts": {
    "dev": "vicinae dev",
    "build": "vicinae build"
  },
  
  "dependencies": {
    "@vicinae/api": "^0.19.0",
    "react": "^18.2.0"
  },
  
  "devDependencies": {
    "@types/node": "^20.0.0",
    "@types/react": "^18.2.0",
    "typescript": "^5.0.0"
  },
  
  "commands": [
    {
      "name": "search",
      "title": "Search Items",
      "description": "Search and browse items",
      "mode": "view",
      "keywords": ["find", "lookup"]
    },
    {
      "name": "create",
      "title": "Create Item",
      "description": "Create a new item",
      "mode": "view",
      "keywords": ["new", "add"]
    }
  ],
  
  "preferences": [
    {
      "name": "apiKey",
      "type": "password",
      "required": true,
      "title": "API Key",
      "description": "Your service API key"
    },
    {
      "name": "theme",
      "type": "dropdown",
      "required": false,
      "title": "Theme",
      "description": "UI theme preference",
      "default": "auto",
      "data": [
        { "title": "Auto", "value": "auto" },
        { "title": "Light", "value": "light" },
        { "title": "Dark", "value": "dark" }
      ]
    },
    {
      "name": "maxResults",
      "type": "textfield",
      "required": false,
      "title": "Max Results",
      "description": "Maximum number of results to show",
      "default": "10"
    }
  ]
}

Commands Configuration

Each command must specify:

  • name - File name in src/ (without .tsx)
  • title - Display name in launcher
  • description - What the command does
  • mode - Always "view" for React components
  • keywords - Optional search aliases

Preferences Types

| Type | Usage | Value Type | |------|-------|-----------| | textfield | Single-line text input | string | | password | Masked text input | string | | dropdown | Select from options | string | | checkbox | Boolean toggle | boolean (string "true"/"false") |

Core UI Components

List - Search & Browse Interface

The most common UI component for searchable item lists:

import { ActionPanel, Action, List, Icon } from '@vicinae/api';
import { useState, useEffect } from 'react';

export default function SearchCommand() {
  const [items, setItems] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const [searchText, setSearchText] = useState('');

  useEffect(() => {
    async function fetchItems() {
      setIsLoading(true);
      const results = await searchAPI(searchText);
      setItems(results);
      setIsLoading(false);
    }
    fetchItems();
  }, [searchText]);

  return (
    
      {items.map((item) => (
        
              
              
            
          }
        />
      ))}
    
  );
}
List Props
interface ListProps {
  children: React.ReactNode;
  isLoading?: boolean;                    // Show loading indicator
  onSearchTextChange?: (text: string) => void;  // Search handler
  throttle?: boolean;                     // Debounce search (default: false)
  isShowingDetail?: boolean;              // Show detail pane
  searchBarAccessory?: React.ReactElement; // Dropdown filter
}
List.Item Props
interface ListItemProps {
  id?: string;
  title: string;
  subtitle?: string;
  accessories?: Accessory[];              // Right-side metadata
  icon?: Icon | string;                   // Left icon
  actions?: React.ReactElement;
  detail?: React.ReactElement;
  keywords?: string[];                    // Search keywords
}

type Accessory = 
  | { text: string; icon?: Icon }
  | { icon: Icon; tooltip?: string }
  | { date: Date }
  | { tag: { value: string; color?: Color } };
List.Item.Detail - Detailed View

  
            
            
            
            
              
            
          
        }
      />
    }
  />
List.Section - Grouped Items

  
    
    
  
  
  
    
  
List.Dropdown - Filter Dropdown

      
      
      
    
  }
>
  {/* List items */}

Grid - Visual Grid Interface

For visual content (images, icons, cards):

import { ActionPanel, Action, Grid } from '@vicinae/api';

export default function GalleryCommand() {
  const [items, setItems] = useState([]);

  return (
    
      
        {items.map((item) => (
          
                
              
            }
          />
        ))}
      
    
  );
}
Grid Props
interface GridProps {
  children: React.ReactNode;
  columns?: number;                       // Number of columns (default: 5)
  aspectRatio?: "1" | "3/2" | "2/3" | "4/3" | "16/9";
  fit?: Grid.Fit;                         // How content fits
  inset?: Grid.Inset;                     // Padding
  isLoading?: boolean;
  searchBarPlaceholder?: string;
  onSearchTextChange?: (text: string) => void;
  throttle?: boolean;
}

enum Grid.Fit {
  Contain = "contain",  // Fit within bounds
  Fill = "fill"         // Fill completely
}

enum Grid.Inset {
  Small = "small",
  Medium = "medium",
  Large = "large"
}

Form - Input Forms

For data collection and settings:

import { ActionPanel, Action, Form, showToast, Toast } from '@vicinae/api';
import { useState } from 'react';

interface FormValues {
  name: string;
  email: string;
  category: string;
  priority: string[];
  notes: string;
  enabled: boolean;
  date: Date;
}

export default function CreateCommand() {
  const [nameError, setNameError] = useState();

  async function handleSubmit(values: FormValues) {
    if (!values.name) {
      setNameError("Name is required");
      return;
    }

    try {
      await createItem(values);
      await showToast({
        style: Toast.Style.Success,
        title: "Item created",
        message: values.name
      });
      // Could use popToRoot() or push() here
    } catch (error) {
      await showToast({
        style: Toast.Style.Failure,
        title: "Failed to create item",
        message: String(error)
      });
    }
  }

  return (
    
          
        
      }
    >
       setNameError(undefined)}
      />
      
      
      
      
        
        
      
      
      
        
        
        
      
      
      
      
      
      
      
      
      
      
      
    
  );
}
Form Field Types

| Component | Purpose | Value Type | |-----------|---------|-----------| | Form.TextField | Single-line text | string | | Form.TextArea | Multi-line text | string | | Form.PasswordField | Masked input | string | | Form.Dropdown | Single selection | string | | Form.TagPicker | Multiple selection | string[] | | Form.Checkbox | Boolean toggle | boolean | | Form.DatePicker | Date selection | Date | | Form.FilePicker | File selection | string[] (paths) | | Form.Separator | Visual divider | - | | Form.Description | Help text | - |

Form Validation
// Real-time validation
 {
    if (!value.includes('@')) {
      setEmailError("Invalid email");
    } else {
      setEmailError(undefined);
    }
  }}
/>

// Submit-time validation
function handleSubmit(values: FormValues) {
  const errors: Record = {};
  
  if (!values.name) errors.name = "Required";
  if (!values.email.includes('@')) errors.email = "Invalid email";
  
  if (Object.keys(errors).length > 0) {
    // Show errors (implementation depends on your approach)
    return;
  }
  
  // Proceed with submission
}

Detail - Markdown Detail View

For displaying detailed information:

import { ActionPanel, Action, Detail } from '@vicinae/api';

export default function ShowDetailCommand() {
  const markdown = `
# Main Title

## Section

Description with **bold** and *italic* text.

\`\`\`typescript
const code = "example";
\`\`\`

- List item 1
- List item 2

[Link](https://example.com)
  `;

  return (
    
          
          
          
          
            
            
          
        
      }
      actions={
        
          
          
        
      }
    />
  );
}

Actions

Actions appear in the ActionPanel and execute commands:

import { ActionPanel, Action, Icon } from '@vicinae/api';

  {/* Built-in Actions */}
  
  
  
  
  
  
  
  
  
  
  
  
  {/* Custom Actions */}
   {
      await performAction();
      await showToast({
        style: Toast.Style.Success,
        title: "Done"
      });
    }}
  />
  
  {/* Form Submit */}
   handleSubmit(values)}
  />
  
  {/* Sections */}
  
     {}} />
  
  
  
     {}} />
  

Keyboard Shortcuts

import { Keyboard } from '@vicinae/api';

 {}}
/>

// Available modifiers: "cmd", "ctrl", "opt", "shift"
// Keys: letters, numbers, "enter", "delete", "escape", "arrowUp", etc.

Navigation

Push and pop between views:

import { List, ActionPanel, Action, Detail, useNavigation } from '@vicinae/api';

function MainList() {
  const { push } = useNavigation();

  return (
    
      
             push()}
            />
          
        }
      />
    
  );
}

function DetailView({ itemId }: { itemId: string }) {
  const { pop } = useNavigation();

  return (
    
          
        
      }
    />
  );
}

Navigation API

const { push, pop } = useNavigation();

// Push new view
push();

// Pop current view
pop();

// Pop to root (close extension)
import { popToRoot } from '@vicinae/api';
await popToRoot();

// Close current window
import { closeMainWindow } from '@vicinae/api';
await closeMainWindow();

Preferences

Access user preferences from package.json:

import { getPreferenceValues } from '@vicinae/api';

interface Preferences {
  apiKey: string;
  theme: string;
  maxResults: string;
}

export default function Command() {
  const preferences = getPreferenceValues();
  
  const apiKey = preferences.apiKey;
  const maxResults = parseInt(preferences.maxResults) || 10;

  // Use preferences...
}

Local Storage

Persist data between sessions:

import { LocalStorage } from '@vicinae/api';

// Store data
await LocalStorage.setItem('key', 'value');
await LocalStorage.setItem('user', JSON.stringify({ id: 1, name: 'Alice' }));

// Retrieve data
const value = await LocalStorage.getItem('key');
const userJson = await LocalStorage.getItem('user');
const user = userJson ? JSON.parse(userJson) : null;

// Remove data
await LocalStorage.removeItem('key');

// Clear all
await LocalStorage.clear();

// Get all items
const allItems: LocalStorage.Values = await LocalStorage.allItems();
// Returns: { key1: 'value1', key2: 'value2', ... }

Toast Notifications

Show feedback to users:

import { showToast, Toast } from '@vicinae/api';

// Success
await showToast({
  style: Toast.Style.Success,
  title: "Operation completed",
  message: "Details about what happened"
});

// Failure
await showToast({
  style: Toast.Style.Failure,
  title: "Operation failed",
  message: "Error details"
});

// Loading (with progress)
const toast = await showToast({
  style: Toast.Style.Animated,
  title: "Processing..."
});

// Update toast
toast.style = Toast.Style.Success;
toast.title = "Completed";
await toast.show();

// Hide toast
await toast.hide();

Toast Styles

enum Toast.Style {
  Success = "success",      // Green checkmark
  Failure = "failure",      // Red X
  Animated = "animated"     // Loading spinner
}

Icons

Built-in icon system:

import { Icon } from '@vicinae/api';

// Common icons:
Icon.Star
Icon.Heart
Icon.Bookmark
Icon.Tag
Icon.Folder
Icon.Document
Icon.Image
Icon.Video
Icon.Music
Icon.Code
Icon.Terminal
Icon.Globe
Icon.Link
Icon.Mail
Icon.Person
Icon.PersonCircle
Icon.Calendar
Icon.Clock
Icon.Bell
Icon.Checkmark
Icon.CheckCircle
Icon.XmarkCircle
Icon.Warning
Icon.Info
Icon.Trash
Icon.Pencil
Icon.Plus
Icon.Minus
Icon.Multiply
Icon.ArrowUp
Icon.ArrowDown
Icon.ArrowLeft
Icon.ArrowRight
Icon.ChevronUp
Icon.ChevronDown
Icon.ChevronLeft
Icon.ChevronRight
Icon.Download
Icon.Upload
Icon.Cloud
Icon.Eye
Icon.EyeSlash
Icon.Lock
Icon.LockUnlocked
Icon.Gear
Icon.Filter
Icon.MagnifyingGlass
Icon.List
Icon.Grid
Icon.Sidebar
Icon.Window
Icon.Play
Icon.Pause
Icon.Stop
Icon.Forward
Icon.Rewind

Colors

Standard color system:

import { Color } from '@vicinae/api';

// Available colors:
Color.Red
Color.Orange
Color.Yellow
Color.Green
Color.Blue
Color.Purple
Color.Magenta
Color.PrimaryText
Color.SecondaryText

Clipboard

Interact with system clipboard:

import { Clipboard } from '@vicinae/api';

// Copy text
await Clipboard.copy("text to copy");

// Copy with metadata (for paste handlers)
await Clipboard.copy("https://example.com", {
  transient: true  // Don't add to clipboard history
});

// Read clipboard
const text = await Clipboard.readText();

// Clear clipboard
await Clipboard.clear();

File Search

Search files indexed by Vicinae:

import { FileSearch } from '@vicinae/api';

const results = await FileSearch.search('document');

results.forEach(file => {
  console.log(file.path);     // Full path
  console.log(file.name);     // File name
});

// Use in List
export default function SearchFilesCommand() {
  const [results, setResults] = useState([]);

  return (
     {
        const files = await FileSearch.search(query);
        setResults(files);
      }}
    >
      {results.map((file) => (
        
              
              
            
          }
        />
      ))}
    
  );
}

Environment

Access environment information:

import { environment } from '@vicinae/api';

console.log(environment.commandName);      // Current command name
console.log(environment.extensionName);    // Extension identifier
console.log(environment.extensionPath);    // Extension directory path
console.log(environment.assetsPath);       // Assets directory path
console.log(environment.supportPath);      // Support files directory

Error Handling

Proper error handling patterns:

import { showToast, Toast } from '@vicinae/api';

export default function Command() {
  const [error, setError] = useState(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [brpaz](https://github.com/brpaz)
- **Source:** [brpaz/agent-skills](https://github.com/brpaz/agent-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.