bun run build # Build current platform
bun run build:all # Build all platforms (linux-x64, linux-arm64, macos-x64, macos-arm64, windows-x64)
bun run build:macos-arm64 # Build specific platform
bun run typecheck # Type checking with TypeScript# Run all tests
bun test
# Run specific test file
bun test src/plugin.test.ts
bun test src/marketplace.test.ts
bun test src/cache.test.ts
bun test src/env-expansion.test.ts
# Run tests with coverage
bun test --coverage# List available plugins
bun run index.ts --list
# Verify output contains "Available plugins:"
bun run index.ts --list | grep "Available plugins"
# Test with specific plugin
bun run index.ts --load playwright@claude-plugins-original
# Test plugin scanning
bun run index.ts --list
# Verify configuration persistence
cat .construct.json- Target: ESNext
- Module resolution: bundler with verbatim syntax
- Strict mode enabled (strict, noUncheckedIndexedAccess, noFallthroughCasesInSwitch, noImplicitOverride)
- NoEmit: true
- Module: Preserve
- Module detection: force
- Node built-ins use explicit
node:prefix:import { join } from "node:path"; - External libraries import directly:
import yargs from "yargs"; - Type imports use explicit
typekeyword:import type { PluginInfo } from "./scanner";
- Interfaces: PascalCase (CliArgs, PluginInfo, ConstructConfig, TranslationResult, PluginComponent)
- Variables: camelCase (cliPlugins, enabledPluginNames, pluginCachePaths)
- Constants: SCREAMING_SNAKE_CASE (CONFIG_FILE = ".construct.json")
- Functions: camelCase (parseCliArgs, scanAllPlugins, loadConfig, saveConfig, mergeCliWithConfig)
cli.ts: CLI argument parsing (yargs)scanner.ts: Plugin discovery and indexing (scanAllPlugins, scanInstalledPlugins, scanMarketplacePlugins)config.ts: Configuration management (.construct.json)translator.ts: Format translation (translatePlugins, expandPluginRootInObject)executor.ts: Copilot subprocess spawningcompletions.ts: Shell completion script generationplugin.ts: Plugin managementmarketplace.ts: Marketplace operationscache.ts: Plugin cachingagent-translator.ts: Agent format translationskill-translator.ts: Skill format translationoperator.ts: Interactive plugin selectorenv-expansion.ts: Environment variable expansion
console.warnfor non-critical errors (missing files, scanning issues, failed agent translation)console.errorfor critical failures (file I/O, parsing errors, MCP config reading)- Try/catch wrap file system operations and JSON parsing
- Functions return
nullon graceful failure (loadConfig, readMcpConfig) - Catch blocks should log errors but not crash the application
- Top-level async function:
async function main(): Promise<void> - Try/catch in async context
- Concurrent operations via
Promise.all() - Use
awaitfor file operations and async function calls
/**
* Represents a single component within a plugin (skill, MCP server, or agent)
*/
export interface PluginComponent {}
/**
* Scans all installed plugins and builds a registry
*/
export async function scanAllPlugins(): Promise<PluginRegistry>- Avoid "any" types - use explicit interfaces
- Use generic type parameters:
<T>,<T extends PluginInfo> - Define return types on all functions
- Use union types for multiple possibilities:
'skill' | 'mcp' | 'agent'
- Imports section
- Type/interface definitions
- Constant/module-level declarations
- Function implementations (exported and private)
Format: <plugin-name>@<marketplace-name>
Example: tmux@scaryrawr-plugins
- Use absolute paths with
join()fromnode:path - Example:
const configPath = join(proc.cwd(), CONFIG_FILE)whereprocis an injected process-like dependency
COPILOT_SKILLS_DIRS: Comma-separated list of skill directoriesCLAUDE_PLUGIN_ROOT: Placeholder for plugin root path (expanded during translation)
- No Plugins Found:
installed_plugins.jsonmissing or empty - Install plugins via Claude Code first - Plugin Not Found: Name mismatch (case-sensitive) - Use exact format from
installed_plugins.json - MCP Servers Not Working: Invalid
.mcp.json- Validate JSON and required fields - Skills Not Loading:
COPILOT_SKILLS_DIRSnot set - Check environment variable construction - Type Errors: Run
bun run typecheckto verify TypeScript configuration
All core modules use optional dependency injection for testability:
config.ts-ConfigDependencieswithfs,processscanner.ts-ScannerDependencieswithfs,processmarketplace.ts-MarketplaceDependencieswithfs,shell, pathscache.ts-CacheDependencieswithfs,process; returnsCacheInstanceplugin.ts-PluginDependencieswith scanner, config, output functionstranslator.ts-TranslatorDependencieswithcache,fsexecutor.ts-ExecutorDependencieswithshell,env
Import from ./test-utils:
import { createMemoryFileSystem, createMockProcess, createMockShell } from './test-utils';- Unit tests (*.test.ts): Use mocks, no I/O, fast
- Integration tests (*.integration.test.ts): Use real file system in temp dirs
import { describe, expect, test } from 'bun:test';
import { createMemoryFileSystem, createMockProcess } from './test-utils';
test('example with mocks', async () => {
const fs = createMemoryFileSystem()
.withFile('/home/.construct.json', '{"enabledPlugins":[]}')
.build();
const proc = createMockProcess({ cwd: '/work', homedir: '/home' });
// Call function with injected deps
const result = await someFunction({ fs, process: proc });
expect(result).toBeDefined();
});