npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kkdev92/vscode-ext-kit

v0.2.2

Published

A lightweight utility library for VS Code extension development

Readme

@kkdev92/vscode-ext-kit

npm version npm downloads CI License: MIT TypeScript

A lightweight, type-safe utility library for VS Code extension development. Eliminates boilerplate and provides consistent patterns for common extension tasks.

Status: Active (best-effort maintenance)

Quick Links: Installation | Quick Start | API Reference | Examples


What's New in v0.2.0

Breaking Change: Logger now uses OutputChannel instead of LogOutputChannel

This change gives you full control over log level filtering. VS Code's previous internal filtering would block debug/trace messages even when your extension's log level allowed them.

Migration: No code changes needed, but log output format now includes [LEVEL] prefix (e.g., [DEBUG] [timestamp] message).


Table of Contents

Features

Core Utilities

  • Logger - Structured logging with full log level control and telemetry support
  • Commands - Simplified command registration with automatic error handling
  • Error Handling - Unified error handling with safeExecute
  • Progress - Step-based progress tracking with cancellation support

Configuration & Storage

  • Config - Type-safe configuration access with validation
  • Storage - Type-safe global/workspace/secret storage wrappers

UI Components

  • UI Utilities - QuickPick, InputBox, and multi-step wizards
  • Notifications - Info/warning/error messages with action buttons
  • Status Bar - Managed status bar items with spinner support

Advanced Features

  • File Watcher - Debounced file watching with event batching
  • Editor - Text editor manipulation utilities
  • Tree View - Base TreeDataProvider with caching
  • WebView - Managed WebView panels with CSP support

Developer Tools

  • Localization - Translation, pluralization, and locale-aware formatting
  • Utilities - retry, debounce, throttle, DisposableCollection

See API Reference below for detailed documentation and code examples.

Installation

npm install @kkdev92/vscode-ext-kit

Requires VS Code 1.96.0+ and Node.js 20.0.0+

Quick Start

import * as vscode from 'vscode';
import {
  createLogger,
  registerCommands,
  withProgress,
  showInfo,
  getSetting,
  onConfigChange,
} from '@kkdev92/vscode-ext-kit';

export function activate(context: vscode.ExtensionContext) {
  // Create logger with config-based level
  const logger = createLogger('MyExtension', {
    level: getSetting('myExtension', 'logLevel', 'info'),
    configSection: 'myExtension.logLevel',
  });
  context.subscriptions.push(logger);

  // Register commands with automatic error handling
  registerCommands(context, logger, {
    'myExtension.helloWorld': () => {
      showInfo('Hello World!');
    },
    'myExtension.processFiles': async () => {
      return withProgress('Processing files...', async (progress, token) => {
        for (let i = 0; i < 10; i++) {
          if (token.isCancellationRequested) return;
          progress.report({ increment: 10, message: `File ${i + 1}/10` });
          await processFile(i);
        }
        return 'Complete';
      }, { cancellable: true });
    },
  });

  logger.info('Extension activated');
}

Examples

Real-World Usage

See these projects using vscode-ext-kit:

  • More examples coming soon!

Code Snippets

Common patterns and usage examples are shown throughout the API Reference below.

Want to add your project? Open a PR!


API Reference

Note: This section contains detailed API documentation. For a quick overview, see Features above.

Logger

Note (v0.2.0): Changed from LogOutputChannel to OutputChannel for full log level control. Log format now includes level prefix: [LEVEL] [timestamp] message

Create a structured logger using VS Code's OutputChannel.

import { createLogger } from '@kkdev92/vscode-ext-kit';

const logger = createLogger('MyExtension', {
  level: 'debug',           // 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'silent'
  showOnError: true,        // Show output channel on error
  timestamp: true,          // Include timestamp in messages
  configSection: 'myExt.logLevel',  // Watch config for level changes
  telemetryReporter: reporter,      // Optional telemetry integration
});

// Logging methods
logger.trace('Detailed trace info', { data: 'value' });
logger.debug('Debug message', 'additional', 'args');
logger.info('User logged in', { userId: 123 });
logger.warn('Deprecated API used');
logger.error('Operation failed', new Error('details'));
logger.error(new Error('Direct error'));

// Dynamic level change
logger.setLevel('warn');

// Cleanup
context.subscriptions.push(logger);

Logging Best Practices

Choose appropriate log levels:

| Level | Use for | |-------|---------| | trace | Detailed debugging (function entry/exit, variable values) | | debug | Development debugging (config values, internal state) | | info | User-relevant events (extension activated, operation completed) | | warn | Recoverable issues (deprecated API, fallback used) | | error | Failures requiring attention |

Tips:

  • Keep info level logs minimal and user-friendly
  • Put detailed diagnostic info at debug or trace level
  • Don't log sensitive data (credentials, personal info)

Commands

Register commands with automatic error handling.

import { registerCommands, registerTextEditorCommands, executeCommand } from '@kkdev92/vscode-ext-kit';

// Register multiple commands
registerCommands(context, logger, {
  'myExt.command1': () => doSomething(),
  'myExt.command2': async (arg1, arg2) => doAsync(arg1, arg2),
}, {
  wrapWithSafeExecute: true,  // Auto-wrap with error handling (default: true)
  commandErrorMessage: (id) => `Command ${id} failed`,
});

// Register text editor commands
registerTextEditorCommands(context, logger, {
  'myExt.formatSelection': (editor, edit) => {
    const selection = editor.selection;
    edit.replace(selection, formatText(editor.document.getText(selection)));
  },
});

// Execute a command programmatically
const result = await executeCommand<string>(logger, 'vscode.openFolder', uri);

Error Handling

Unified error handling with logging and notifications.

import { safeExecute, trySafeExecute } from '@kkdev92/vscode-ext-kit';

// Basic usage - returns undefined on error
const result = await safeExecute(logger, 'Fetch data', async () => {
  return await fetchData();
});

// With options
const data = await safeExecute(logger, 'Process file', async () => {
  return await processFile(path);
}, {
  userMessage: 'Failed to process file',  // Custom error message for user
  silent: false,                          // Show notification (default: false)
  rethrow: false,                         // Rethrow after logging (default: false)
});

// Using Result type for explicit success/failure
const result = await trySafeExecute(logger, 'Operation', async () => {
  return await riskyOperation();
});

if (result.success) {
  console.log(result.value);
} else {
  console.log(result.error);
}

Progress

Display progress notifications with cancellation support.

import { withProgress, withSteps, toAbortSignal } from '@kkdev92/vscode-ext-kit';
import { ProgressLocation } from 'vscode';

// With progress reporting
const result = await withProgress('Processing...', async (progress, token) => {
  for (let i = 0; i < 100; i++) {
    if (token.isCancellationRequested) {
      return undefined;
    }
    progress.report({
      increment: 1,
      message: `Step ${i + 1}/100`
    });
    await processStep(i);
  }
  return 'Complete';
}, {
  location: ProgressLocation.Notification,  // Default
  cancellable: true,
});

// Simple progress without reporting
const data = await withProgress('Loading...', async () => {
  return await loadData();
});

Step-based Progress

Execute multiple steps with automatic progress tracking.

import { withSteps } from '@kkdev92/vscode-ext-kit';

// Basic usage
const result = await withSteps('Deploying...', [
  { label: 'Building', task: async () => await build() },
  { label: 'Testing', task: async () => await runTests() },
  { label: 'Publishing', task: async () => await publish() },
]);

if (result.completed) {
  console.log('All steps completed');
}

// With weights (heavier steps show more progress)
const result = await withSteps('Processing...', [
  { label: 'Downloading', task: download, weight: 3 },
  { label: 'Processing', task: process, weight: 5 },
  { label: 'Uploading', task: upload, weight: 2 },
], { cancellable: true });

// Access individual step results
const [downloadResult, processResult, uploadResult] = result.results;

// Handle cancellation
if (result.cancelled) {
  console.log('Operation was cancelled');
}

AbortSignal Integration

Convert VS Code CancellationToken to AbortSignal for fetch APIs.

import { withProgress, toAbortSignal } from '@kkdev92/vscode-ext-kit';

await withProgress('Fetching...', async (progress, token) => {
  const signal = toAbortSignal(token);
  const response = await fetch(url, { signal });
  return await response.json();
}, { cancellable: true });

Configuration

Type-safe configuration access.

import { getSetting, setSetting, onConfigChange, getConfig } from '@kkdev92/vscode-ext-kit';

// Get a setting with default value
const level = getSetting('myExtension', 'logLevel', 'info');
const timeout = getSetting('myExtension', 'timeout', 5000);

// Set a setting
await setSetting('myExtension', 'logLevel', 'debug', ConfigurationTarget.Global);

// Watch for configuration changes
context.subscriptions.push(
  onConfigChange('myExtension', (e) => {
    if (e.affectsConfiguration('myExtension.logLevel')) {
      logger.setLevel(getSetting('myExtension', 'logLevel', 'info'));
    }
  })
);

// Get entire configuration section
const config = getConfig('myExtension');

UI Utilities

QuickPick, InputBox, and multi-step wizard helpers.

import { pickOne, pickMany, inputText, wizard } from '@kkdev92/vscode-ext-kit';

// Single selection
const item = await pickOne(
  [
    { label: 'Option 1', value: 1 },
    { label: 'Option 2', value: 2 },
  ],
  { placeHolder: 'Select an option' }
);

// Multiple selection
const items = await pickMany(
  [
    { label: 'File 1', picked: true },
    { label: 'File 2' },
  ],
  { placeHolder: 'Select files' }
);

// Text input with validation
const name = await inputText({
  prompt: 'Enter project name',
  placeHolder: 'my-project',
  validate: (value) => {
    if (!value) return 'Name is required';
    if (!/^[a-z-]+$/.test(value)) return 'Use lowercase letters and hyphens only';
    return undefined;
  },
});

Multi-Step Wizard

Create guided multi-step input flows with back navigation, step tracking, and conditional skipping.

import { wizard } from '@kkdev92/vscode-ext-kit';

interface BranchState {
  type: 'feature' | 'fix' | 'chore';
  name: string;
  description: string;
}

const result = await wizard<BranchState>({
  title: 'Create Branch',
  steps: [
    {
      id: 'type',
      type: 'quickpick',
      placeholder: 'Select branch type',
      items: [
        { label: 'Feature', description: 'New feature', value: 'feature' },
        { label: 'Bug Fix', description: 'Fix a bug', value: 'fix' },
        { label: 'Chore', description: 'Maintenance', value: 'chore' },
      ],
    },
    {
      id: 'name',
      type: 'input',
      prompt: 'Enter branch name',
      placeholder: 'my-feature',
      validate: (value) => {
        if (!/^[a-z0-9-]+$/.test(value)) {
          return 'Use lowercase letters, numbers, and hyphens only';
        }
        return undefined;
      },
    },
    {
      id: 'description',
      type: 'input',
      prompt: 'Enter description (optional)',
      // Skip this step for chore branches
      skip: (state) => state.type === 'chore',
    },
  ],
});

if (result.completed) {
  const { type, name, description } = result.state as BranchState;
  await createBranch(`${type}/${name}`, description);
}

Wizard features:

  • Step numbers (e.g., "Step 1 of 3")
  • Back button navigation
  • Conditional step skipping based on previous inputs
  • Dynamic items and default values based on state
  • Input validation with state context

Notifications

Show notifications with action buttons.

import { showInfo, showWarn, showError, confirm, showWithActions } from '@kkdev92/vscode-ext-kit';

// Simple notifications
await showInfo('Operation completed');
await showWarn('This action is deprecated');
await showError('Failed to save file');

// Confirmation dialog
const confirmed = await confirm('Delete this file?', 'Delete', 'Cancel');
if (confirmed) {
  await deleteFile();
}

// Notifications with custom actions
const action = await showWithActions(
  'info',
  'New version available',
  [
    { label: 'Update Now', value: 'update' },
    { label: 'Later', value: 'later' },
    { label: 'Never', value: 'never' },
  ]
);

Status Bar

Managed status bar items.

import { createStatusBarItem, showStatusMessage } from '@kkdev92/vscode-ext-kit';

// Create managed status bar item
const statusItem = createStatusBarItem({
  id: 'myExt.status',
  text: 'Ready',
  tooltip: 'Extension status',
  command: 'myExt.showStatus',
  alignment: 'left',
  priority: 100,
});

// Update status
statusItem.setText('Processing...');
statusItem.showSpinner();
// ... do work ...
statusItem.hideSpinner();
statusItem.setText('Ready');

// Temporary status message
const disposable = showStatusMessage('Saved!', 3000);

context.subscriptions.push(statusItem);

Storage

Type-safe storage wrappers.

import { createGlobalStorage, createWorkspaceStorage, createSecretStorage } from '@kkdev92/vscode-ext-kit';

// Global storage (persists across workspaces)
const globalStorage = createGlobalStorage<{ theme: string; fontSize: number }>(
  context,
  'settings',
  {
    defaultValue: { theme: 'dark', fontSize: 14 },
    version: 2,
    migrate: (old, version) => {
      if (version === 1) {
        return { ...old, fontSize: 14 };
      }
      return old;
    },
  }
);

const settings = globalStorage.get();
await globalStorage.set({ theme: 'light', fontSize: 16 });
await globalStorage.reset();

// Workspace storage (per-workspace)
const workspaceStorage = createWorkspaceStorage<string[]>(
  context,
  'recentFiles',
  { defaultValue: [] }
);

// Secret storage (encrypted)
const secretStorage = createSecretStorage(context, 'apiKey');
await secretStorage.set('sk-...');
const apiKey = await secretStorage.get();
await secretStorage.delete();

// Listen for changes
secretStorage.onDidChange(() => {
  console.log('API key changed');
});

context.subscriptions.push(secretStorage);

File Watcher

Debounced file watching with event batching.

import { createFileWatcher, watchFile } from '@kkdev92/vscode-ext-kit';

// Watch multiple patterns with debouncing
const watcher = createFileWatcher({
  patterns: ['**/*.ts', '**/*.json'],
  ignorePatterns: ['**/node_modules/**'],
  debounceDelay: 300,
  events: ['create', 'change', 'delete'],
  workspaceFolder: vscode.workspace.workspaceFolders?.[0],  // Use RelativePattern
}, {
  onCreate: (uri) => console.log('Created:', uri.fsPath),
  onChange: (uri) => console.log('Changed:', uri.fsPath),
  onDelete: (uri) => console.log('Deleted:', uri.fsPath),
});

context.subscriptions.push(watcher);

// Simple single-file watcher
const configWatcher = watchFile('**/.myconfig', (uri) => {
  reloadConfig();
});

Editor Utilities

Text editor manipulation utilities.

import {
  getSelectedText,
  replaceText,
  insertAtCursor,
  transformSelection,
  getLine,
  getCurrentLine,
  moveCursor,
  selectRange,
} from '@kkdev92/vscode-ext-kit';

// Get selected text
const text = getSelectedText(editor);

// Replace text in range
await replaceText(editor, range, 'new text');

// Insert at cursor
await insertAtCursor(editor, 'inserted text');

// Transform selection
await transformSelection(editor, (text) => text.toUpperCase());

// Get line content
const line = getLine(editor, 5);
const currentLine = getCurrentLine(editor);

// Cursor and selection manipulation
moveCursor(editor, new vscode.Position(10, 0));
selectRange(editor, new vscode.Range(0, 0, 10, 0));

Tree View

Base class for tree data providers.

import { BaseTreeDataProvider, createTreeView } from '@kkdev92/vscode-ext-kit';

interface FileItem {
  name: string;
  path: string;
  isDirectory: boolean;
}

class FileTreeProvider extends BaseTreeDataProvider<FileItem> {
  async fetchRoots(): Promise<FileItem[]> {
    return await this.loadDirectory('/');
  }

  async fetchChildren(item: FileItem): Promise<FileItem[]> {
    if (!item.isDirectory) return [];
    return await this.loadDirectory(item.path);
  }

  createTreeItem(item: FileItem): vscode.TreeItem {
    const treeItem = new vscode.TreeItem(
      item.name,
      item.isDirectory
        ? vscode.TreeItemCollapsibleState.Collapsed
        : vscode.TreeItemCollapsibleState.None
    );
    treeItem.contextValue = item.isDirectory ? 'directory' : 'file';
    return treeItem;
  }

  getItemId(item: FileItem): string {
    return item.path;
  }
}

const provider = new FileTreeProvider();
const treeView = createTreeView('myExtension.fileTree', provider, {
  showCollapseAll: true,
});

context.subscriptions.push(treeView);

WebView

Managed WebView panels with CSP support.

import { createWebViewPanel, generateCSP, createWebViewHtml } from '@kkdev92/vscode-ext-kit';

const panel = createWebViewPanel({
  viewType: 'myExt.preview',
  title: 'Preview',
  column: vscode.ViewColumn.Beside,
  enableScripts: true,
  retainContextWhenHidden: true,
  localResourceRoots: [context.extensionUri],
});

// Generate CSP
const csp = generateCSP({
  nonce: panel.nonce,
  webview: panel.webview,
  allowUnsafeInline: false,
});

// Set HTML content
panel.webview.html = createWebViewHtml({
  webview: panel.webview,
  extensionUri: context.extensionUri,
  title: 'Preview',
  body: '<div id="app"></div>',
  scripts: ['dist/webview.js'],
  styles: ['dist/webview.css'],
});

// Handle messages from webview
panel.onMessage((message) => {
  if (message.command === 'save') {
    saveData(message.data);
  }
});

// Send messages to webview
panel.postMessage({ command: 'update', data: newData });

context.subscriptions.push(panel);

Utilities

DisposableCollection

import { DisposableCollection } from '@kkdev92/vscode-ext-kit';

const disposables = new DisposableCollection();

disposables.push(
  vscode.workspace.onDidChangeConfiguration(() => {}),
  vscode.window.onDidChangeActiveTextEditor(() => {})
);

const watcher = disposables.add(
  vscode.workspace.createFileSystemWatcher('**/*.ts')
);

context.subscriptions.push(disposables);

Retry

import { retry } from '@kkdev92/vscode-ext-kit';

const data = await retry(
  () => fetchWithTimeout(url),
  {
    maxAttempts: 5,
    delay: 1000,
    backoff: 'exponential',
    retryIf: (error) => error.code === 'ETIMEDOUT',
    onRetry: (error, attempt, delay) => {
      logger.warn(`Retry ${attempt} after ${delay}ms`, error);
    },
  }
);

Debounce / Throttle

import { debounce, throttle } from '@kkdev92/vscode-ext-kit';

// Debounce - execute after delay since last call
const debouncedSave = debounce((content: string) => {
  saveToFile(content);
}, 500);

editor.onDidChangeTextDocument(() => {
  debouncedSave(editor.document.getText());
});

// Throttle - execute at most once per interval
const throttledUpdate = throttle(() => {
  updatePreview();
}, 100);

editor.onDidScrollChange(() => {
  throttledUpdate();
});

// Cancel pending executions
debouncedSave.cancel();

Localization

import { t, getLanguage, isLanguage, plural, formatNumber, formatDate, formatRelativeTime } from '@kkdev92/vscode-ext-kit';

// Translate message (uses vscode.l10n.t)
const greeting = t('Hello, World!');
const welcome = t('Welcome, {0}!', userName);

// Check current language
const lang = getLanguage(); // 'en', 'ja', etc.
if (isLanguage('ja')) {
  // Japanese-specific handling
}

// Pluralization (uses Intl.PluralRules)
plural(1, { one: '{count} item', other: '{count} items' }); // "1 item"
plural(5, { one: '{count} item', other: '{count} items' }); // "5 items"
plural(0, {
  zero: 'No items',
  one: '{count} item',
  other: '{count} items'
}); // "No items"

// Number formatting (locale-aware)
formatNumber(1234567.89);  // "1,234,567.89" (en) / "1.234.567,89" (de)
formatNumber(0.75, { style: 'percent' });  // "75%"
formatNumber(1234.56, { style: 'currency', currency: 'USD' });  // "$1,234.56"
formatNumber(3.14159, { maximumFractionDigits: 2 });  // "3.14"

// Date formatting (locale-aware)
formatDate(new Date(), { dateStyle: 'short' });  // "2/4/26" (en-US)
formatDate(new Date(), { dateStyle: 'long' });   // "February 4, 2026" (en)
formatDate(new Date(), { dateStyle: 'medium', timeStyle: 'short' });

// Relative time formatting
formatRelativeTime(-1, 'day');    // "1 day ago" (en) / "1日前" (ja)
formatRelativeTime(2, 'hour');    // "in 2 hours"
formatRelativeTime(-5, 'minute', 'short');  // "5 min. ago"

Troubleshooting

Common Issues

Logger not showing debug messages

Problem: Debug logs don't appear in Output channel.

Solution: Check your log level setting:

// Ensure level is 'debug' or 'trace'
const logger = createLogger('MyExt', { level: 'debug' });

TypeScript errors with imports

Problem: Cannot find module '@kkdev92/vscode-ext-kit'

Solution: Ensure you have the correct version:

npm install @kkdev92/vscode-ext-kit@latest

Commands not registering

Problem: Commands don't appear in Command Palette.

Solution: Verify package.json includes command declarations:

{
  "contributes": {
    "commands": [
      { "command": "myExt.commandId", "title": "My Command" }
    ]
  }
}

Need More Help?


Development

Prerequisites

  • Node.js >= 20.0.0
  • npm >= 10.0.0

Setup

git clone https://github.com/kkdev92/vscode-ext-kit.git
cd vscode-ext-kit
npm install

Scripts

| Script | Description | |--------|-------------| | npm run build | Compile TypeScript to dist/ | | npm test | Run tests with Vitest | | npm run test:watch | Run tests in watch mode | | npm run test:coverage | Run tests with coverage | | npm run lint | Run ESLint | | npm run lint:fix | Fix ESLint errors | | npm run format | Format code with Prettier | | npm run typecheck | Run TypeScript type checking | | npm run knip | Check for unused code/exports |

Git Hooks

Pre-commit hooks automatically run:

  1. lint-staged - Formats and lints staged files
  2. typecheck - Verifies TypeScript types
  3. test - Runs the test suite

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

For security concerns, please see SECURITY.md.


License

MIT