OpenWriterControle/plugins/claude-code/scripts/install-context.js
Darren Hinde f84f32b7b3
fix: overhaul Claude Code plugin for correctness and spec compliance (#225)
- Fix critical bugs: coder-agent name (CoderAgent→coder-agent), manifest
  path in session-start.sh, /oac:setup ghost command replaced with /install-context
- Fix hooks.json: add timeout/async, add description, restore matcher
- Fix session-start.sh: dual-format output (Claude Code + Cursor/OpenCode),
  remove invalid JSON escapes for $ and backtick
- Fix agent spec compliance: disallowedTools on read-only agents, WebFetch
  on external-scout, haiku model on context-scout, example blocks on 3 agents
- Fix skill descriptions: triggering conditions only (no workflow summaries)
  per superpowers empirical testing — prevents Claude shortcutting skill content
- Add Red Flags + Rationalization tables to 4 discipline skills
- Add install-context skill + command replacing oac-setup/oac-plan/oac-add-context
- Add cleanup-tmp.sh (was missing, broke /oac:cleanup)
- Add TypeScript installer (install-context.ts/js) with profile-based download
- Update plugin.json: keywords, homepage for marketplace discoverability
- Update README: correct skill count, fix structure diagram, remove stale refs
- Remove obsolete files: FIRST-TIME-SETUP.md, INSTALL.md, QUICK-START.md,
  download-context.sh, verify-context.sh, load-config.sh, context-manager skill,
  oac-setup/oac-plan/oac-add-context commands
2026-02-19 20:16:17 +00:00

328 lines
8.5 KiB
JavaScript
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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();