| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- #!/usr/bin/env node
- /**
- * install-context.js
- * Simple context installer for OAC Claude Code Plugin
- *
- * Downloads context files from OpenAgents Control repository
- * Supports profile-based installation (core, full, custom)
- */
- const { execSync } = require('child_process');
- const { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } = require('fs');
- const { join } = require('path');
- // Configuration
- const GITHUB_REPO = 'darrenhinde/OpenAgentsControl';
- const GITHUB_BRANCH = 'main';
- const CONTEXT_SOURCE_PATH = '.opencode/context';
- const PLUGIN_ROOT = process.env.CLAUDE_PLUGIN_ROOT || process.cwd();
- const CONTEXT_DIR = join(PLUGIN_ROOT, 'context');
- const MANIFEST_FILE = join(PLUGIN_ROOT, '.context-manifest.json');
- // Installation profiles
- const PROFILES = {
- core: {
- name: 'Core',
- description: 'Essential standards and workflows',
- categories: ['core', 'openagents-repo']
- },
- full: {
- name: 'Full',
- description: 'All available context',
- categories: [
- 'core',
- 'openagents-repo',
- 'development',
- 'ui',
- 'content-creation',
- 'data',
- 'product',
- 'learning',
- 'project',
- 'project-intelligence'
- ]
- }
- };
- // Colors for output
- const colors = {
- reset: '\x1b[0m',
- red: '\x1b[31m',
- green: '\x1b[32m',
- yellow: '\x1b[33m',
- blue: '\x1b[34m',
- };
- // Logging helpers
- const log = {
- info: (msg) => console.log(`${colors.blue}ℹ${colors.reset} ${msg}`),
- success: (msg) => console.log(`${colors.green}✓${colors.reset} ${msg}`),
- warning: (msg) => console.log(`${colors.yellow}⚠${colors.reset} ${msg}`),
- error: (msg) => console.error(`${colors.red}✗${colors.reset} ${msg}`),
- };
- /**
- * Check if required commands are available
- */
- function checkDependencies() {
- const required = ['git'];
- const missing = [];
- for (const cmd of required) {
- try {
- execSync(`command -v ${cmd}`, { stdio: 'ignore' });
- } catch {
- missing.push(cmd);
- }
- }
- if (missing.length > 0) {
- log.error(`Missing required dependencies: ${missing.join(', ')}`);
- log.info('Install with: brew install ' + missing.join(' '));
- process.exit(1);
- }
- }
- /**
- * Download context using git sparse-checkout
- */
- function downloadContext(categories) {
- const tempDir = join(PLUGIN_ROOT, '.tmp-context-download');
- try {
- log.info(`Downloading context from ${GITHUB_REPO}...`);
- log.info(`Categories: ${categories.join(', ')}`);
- // Clean up temp directory if it exists
- if (existsSync(tempDir)) {
- rmSync(tempDir, { recursive: true, force: true });
- }
- // Clone with sparse checkout (no working tree files)
- log.info('Cloning repository...');
- execSync(
- `git clone --depth 1 --filter=blob:none --sparse https://github.com/${GITHUB_REPO}.git "${tempDir}"`,
- { stdio: 'pipe' }
- );
- // Configure sparse checkout for requested categories
- log.info('Configuring sparse checkout...');
- const sparseCheckoutPaths = categories.map(cat => `${CONTEXT_SOURCE_PATH}/${cat}`);
-
- // Also include root navigation
- sparseCheckoutPaths.push(`${CONTEXT_SOURCE_PATH}/navigation.md`);
- execSync(
- `cd "${tempDir}" && git sparse-checkout set ${sparseCheckoutPaths.join(' ')}`,
- { stdio: 'pipe' }
- );
- // Create context directory if it doesn't exist
- if (!existsSync(CONTEXT_DIR)) {
- mkdirSync(CONTEXT_DIR, { recursive: true });
- }
- // Copy files to context directory
- log.info('Copying context files...');
- const sourceContextDir = join(tempDir, CONTEXT_SOURCE_PATH);
-
- if (existsSync(sourceContextDir)) {
- execSync(`cp -r "${sourceContextDir}"/* "${CONTEXT_DIR}"/`, { stdio: 'pipe' });
- log.success('Context files downloaded successfully');
- } else {
- throw new Error('Context directory not found in repository');
- }
- // Count downloaded files
- const fileCount = execSync(`find "${CONTEXT_DIR}" -type f | wc -l`, { encoding: 'utf-8' }).trim();
- log.success(`Downloaded ${fileCount} files`);
- // Clean up temp directory
- rmSync(tempDir, { recursive: true, force: true });
- } catch (error) {
- log.error('Failed to download context');
- if (error instanceof Error) {
- log.error(error.message);
- }
-
- // Clean up on error
- if (existsSync(tempDir)) {
- rmSync(tempDir, { recursive: true, force: true });
- }
-
- process.exit(1);
- }
- }
- /**
- * Create manifest file tracking installation
- */
- function createManifest(profile, categories) {
- try {
- // Get commit SHA
- const tempDir = join(PLUGIN_ROOT, '.tmp-manifest-check');
-
- if (existsSync(tempDir)) {
- rmSync(tempDir, { recursive: true, force: true });
- }
- execSync(
- `git clone --depth 1 https://github.com/${GITHUB_REPO}.git "${tempDir}"`,
- { stdio: 'pipe' }
- );
- const commitSha = execSync(`cd "${tempDir}" && git rev-parse HEAD`, { encoding: 'utf-8' }).trim();
-
- rmSync(tempDir, { recursive: true, force: true });
- // Count files per category
- const files = {};
- for (const category of categories) {
- const categoryPath = join(CONTEXT_DIR, category);
- if (existsSync(categoryPath)) {
- const count = parseInt(
- execSync(`find "${categoryPath}" -type f | wc -l`, { encoding: 'utf-8' }).trim(),
- 10
- );
- files[category] = count;
- }
- }
- // Create manifest
- const manifest = {
- version: '1.0.0',
- profile,
- source: {
- repository: GITHUB_REPO,
- branch: GITHUB_BRANCH,
- commit: commitSha,
- downloaded_at: new Date().toISOString(),
- },
- categories,
- files,
- };
- writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
- log.success(`Created manifest: ${MANIFEST_FILE}`);
- } catch (error) {
- log.warning('Failed to create manifest (non-fatal)');
- }
- }
- /**
- * Show usage information
- */
- function showUsage() {
- console.log(`
- Usage: node install-context.js [PROFILE] [OPTIONS]
- PROFILES:
- core Download core context only (default)
- Categories: core, openagents-repo
-
- full Download all available context
- Categories: all domains
- custom Specify custom categories
- Use with --category flags
- OPTIONS:
- --category=NAME Add specific category (use with 'custom' profile)
- --force Force re-download even if context exists
- --help Show this help message
- EXAMPLES:
- # Install core context (default)
- node install-context.js
- # Install full context
- node install-context.js full
- # Install custom categories
- node install-context.js custom --category=core --category=development
- # Force reinstall
- node install-context.js core --force
- `);
- }
- /**
- * Main installation function
- */
- function main() {
- const args = process.argv.slice(2);
- // Parse arguments
- let profile = 'core';
- let customCategories = [];
- let force = false;
- for (const arg of args) {
- if (arg === '--help' || arg === '-h') {
- showUsage();
- process.exit(0);
- } else if (arg === '--force') {
- force = true;
- } else if (arg.startsWith('--category=')) {
- customCategories.push(arg.split('=')[1]);
- } else if (arg === 'core' || arg === 'full' || arg === 'custom') {
- profile = arg;
- } else {
- log.error(`Unknown argument: ${arg}`);
- showUsage();
- process.exit(1);
- }
- }
- // Determine categories to install
- let categories;
- if (profile === 'custom') {
- if (customCategories.length === 0) {
- log.error('Custom profile requires --category flags');
- showUsage();
- process.exit(1);
- }
- categories = customCategories;
- } else {
- categories = PROFILES[profile].categories;
- }
- // Check if context already exists
- if (existsSync(MANIFEST_FILE) && !force) {
- log.warning('Context already installed. Use --force to reinstall.');
- log.info('Current installation:');
- try {
- const manifest = JSON.parse(readFileSync(MANIFEST_FILE, 'utf-8'));
- console.log(JSON.stringify(manifest, null, 2));
- } catch {
- log.error('Failed to read manifest');
- }
- process.exit(0);
- }
- // Check dependencies
- checkDependencies();
- // Download context
- console.log('');
- log.info(`Installing ${profile} profile`);
- log.info(`Repository: ${GITHUB_REPO}`);
- log.info(`Branch: ${GITHUB_BRANCH}`);
- console.log('');
- downloadContext(categories);
- // Create manifest
- createManifest(profile, categories);
- console.log('');
- log.success('Context installation complete!');
- log.info(`Context location: ${CONTEXT_DIR}`);
- log.info(`Manifest: ${MANIFEST_FILE}`);
- console.log('');
- }
- // Run main function
- main();
|