OpenWriterControle/plugins/claude-code/scripts/install-context.js
Darren Hinde a8fc9c7715
feat(plugin): enhance context management and skill invocation (#239)
* fix(shellcheck): declare and assign separately in router.sh (SC2155)

* feat(plugin): enhance context management and skill invocation

- Update .context-manifest.json to include profile and detailed source information.
- Revise context-scout.md to clarify context root discovery and navigation.
- Modify commands to replace deprecated skills with updated ones (brainstorming → approach, systematic-debugging → debugger).
- Streamline install-context.md for clarity and remove interactive profile selection.
- Improve oac-status.md to provide a more comprehensive status report.
- Add OAC system paths to session-start.sh for better context management.
- Remove obsolete skills and improve documentation for existing commands.
2026-02-23 12:12:29 +00:00

281 lines
8.2 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
* Downloads OAC context files to .claude/context/ in the current project.
*
* Requirements: node, git (nothing else to install)
*
* Run from project root:
* node install-context.js [--profile=standard] [--force] [--dry-run]
*/
const { execSync } = require('child_process');
const { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync, mkdtempSync } = require('fs');
const { join } = require('path');
const os = require('os');
// Configuration
const GITHUB_REPO = 'darrenhinde/OpenAgentsControl';
const GITHUB_BRANCH = 'main';
const CONTEXT_SOURCE_PATH = '.opencode/context';
// Roots resolved after parsing --global flag in main()
const PROJECT_ROOT = process.cwd();
const GLOBAL_ROOT = join(os.homedir(), '.claude');
// Installation profiles
const PROFILES = {
core: {
categories: ['core', 'openagents-repo'],
},
full: {
categories: [
'core',
'openagents-repo',
'development',
'ui',
'content-creation',
'data',
'product',
'learning',
'project',
'project-intelligence',
],
},
};
// Map user-facing profile names → internal profile names
const PROFILE_MAP = {
'essential': 'core',
'standard': 'core',
'extended': 'full',
'specialized': 'full',
'all': 'full',
'core': 'core',
'full': 'full',
};
// Colors
const c = {
reset: '\x1b[0m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
};
const log = {
info: (msg) => console.log(`${c.blue}ℹ${c.reset} ${msg}`),
success: (msg) => console.log(`${c.green}✓${c.reset} ${msg}`),
warning: (msg) => console.log(`${c.yellow}⚠${c.reset} ${msg}`),
error: (msg) => console.error(`${c.red}✗${c.reset} ${msg}`),
};
function checkDependencies() {
try {
execSync('command -v git', { stdio: 'ignore' });
} catch {
log.error('git is required but not found.');
log.info('Install: brew install git (Mac) or sudo apt install git (Linux)');
process.exit(1);
}
}
/**
* Download context via git sparse-checkout.
* Returns the commit SHA of the downloaded content.
*/
function downloadContext(categories, contextDir) {
const tempDir = mkdtempSync(join(os.tmpdir(), 'oac-context-'));
try {
log.info(`Downloading from ${GITHUB_REPO}...`);
log.info('Cloning repository...');
execSync(
`git clone --depth 1 --filter=blob:none --sparse https://github.com/${GITHUB_REPO}.git "${tempDir}"`,
{ stdio: 'pipe' }
);
const commitSha = execSync(`git -C "${tempDir}" rev-parse HEAD`, { encoding: 'utf-8' }).trim();
log.info('Configuring sparse checkout...');
const sparsePaths = [
...categories.map(cat => `${CONTEXT_SOURCE_PATH}/${cat}`),
`${CONTEXT_SOURCE_PATH}/navigation.md`,
];
execSync(
`git -C "${tempDir}" sparse-checkout set --skip-checks ${sparsePaths.join(' ')}`,
{ stdio: 'pipe' }
);
log.info('Copying context files...');
mkdirSync(contextDir, { recursive: true });
const sourceDir = join(tempDir, CONTEXT_SOURCE_PATH);
if (!existsSync(sourceDir)) {
throw new Error('Context directory not found in repository');
}
execSync(`cp -r "${sourceDir}/"* "${contextDir}/"`, { stdio: 'pipe' });
const fileCount = execSync(`find "${contextDir}" -type f | wc -l`, { encoding: 'utf-8' }).trim();
log.success(`Downloaded ${fileCount.trim()} files`);
return commitSha;
} catch (error) {
log.error('Failed to download context');
if (error instanceof Error) log.error(error.message);
process.exit(1);
} finally {
rmSync(tempDir, { recursive: true, force: true });
}
}
function createManifest(profile, categories, commitSha, contextDir, manifestFile) {
const files = {};
for (const category of categories) {
const categoryPath = join(contextDir, category);
if (existsSync(categoryPath)) {
files[category] = parseInt(
execSync(`find "${categoryPath}" -type f | wc -l`, { encoding: 'utf-8' }).trim(),
10
);
}
}
const manifest = {
version: '1.0.0',
profile,
source: {
repository: GITHUB_REPO,
branch: GITHUB_BRANCH,
commit: commitSha,
downloaded_at: new Date().toISOString(),
},
categories,
files,
};
mkdirSync(join(manifestFile, '..'), { recursive: true });
writeFileSync(manifestFile, JSON.stringify(manifest, null, 2));
log.success(`Manifest created: ${manifestFile}`);
}
function showUsage() {
console.log(`
Usage: node install-context.js [OPTIONS]
Downloads OAC context files. Requirements: node, git (nothing else to install)
Works on Mac, Linux, and Windows.
OPTIONS:
--profile=NAME Profile: essential, standard, extended, all (default: standard)
--global Install to ~/.claude/context/ (all projects share it)
--force Re-download even if already installed
--dry-run Show what would be installed without downloading
--help Show this help
PROFILES:
essential/standard Core context (core + openagents-repo)
extended/all Full context (all categories)
SCOPE:
default (no flag) Installs to .claude/context/ in the current project
--global Installs to ~/.claude/context/ for all projects
`);
}
function main() {
const args = process.argv.slice(2);
let profileName = 'standard';
let customCategories = [];
let isGlobal = false;
let force = false;
let dryRun = false;
for (const arg of args) {
if (arg === '--help' || arg === '-h') {
showUsage();
process.exit(0);
} else if (arg === '--force') {
force = true;
} else if (arg === '--dry-run') {
dryRun = true;
} else if (arg === '--global') {
isGlobal = true;
} else if (arg.startsWith('--profile=')) {
profileName = arg.split('=')[1];
if (!PROFILE_MAP[profileName]) {
log.error(`Unknown profile: ${profileName}`);
log.info(`Valid profiles: ${Object.keys(PROFILE_MAP).join(', ')}`);
process.exit(1);
}
} else if (arg.startsWith('--category=')) {
customCategories.push(arg.split('=')[1]);
} else if (PROFILE_MAP[arg]) {
profileName = arg;
} else {
log.error(`Unknown argument: ${arg}`);
showUsage();
process.exit(1);
}
}
// Resolve install targets based on scope
const installRoot = isGlobal ? GLOBAL_ROOT : join(PROJECT_ROOT, '.claude');
const CONTEXT_DIR = join(installRoot, 'context');
const MANIFEST_FILE = join(installRoot, '.context-manifest.json');
const scopeLabel = isGlobal ? 'global (~/.claude/context)' : 'project (.claude/context)';
const categories = customCategories.length > 0
? (profileName = 'custom', customCategories)
: PROFILES[PROFILE_MAP[profileName] || 'core'].categories;
// Already installed?
if (existsSync(MANIFEST_FILE) && !force) {
log.warning(`Context already installed at ${scopeLabel}. Use --force to reinstall.`);
try {
const manifest = JSON.parse(readFileSync(MANIFEST_FILE, 'utf-8'));
log.info(`Profile: ${manifest.profile}, installed: ${manifest.source?.downloaded_at?.slice(0, 10)}`);
} catch { /* ignore */ }
process.exit(0);
}
checkDependencies();
console.log('');
log.info(`Scope: ${scopeLabel}`);
log.info(`Profile: ${profileName}`);
log.info(`Categories: ${categories.join(', ')}`);
log.info(`Target: ${CONTEXT_DIR}`);
console.log('');
if (dryRun) {
log.info('Dry run — no files downloaded');
return;
}
const commitSha = downloadContext(categories, CONTEXT_DIR);
createManifest(profileName, categories, commitSha, CONTEXT_DIR, MANIFEST_FILE);
if (isGlobal) {
log.info('Global install — no .oac.json needed (discovery chain finds ~/.claude/context automatically)');
} else {
const oacJson = join(PROJECT_ROOT, '.oac.json');
if (!existsSync(oacJson)) {
writeFileSync(oacJson, JSON.stringify({ version: '1', context: { root: '.claude/context' } }, null, 2));
log.success('.oac.json created at project root');
} else {
log.info('.oac.json already exists — skipping');
}
}
console.log('');
log.success('Context installation complete!');
log.info(`Scope: ${scopeLabel}`);
log.info(`Context: ${CONTEXT_DIR}`);
log.info(`Manifest: ${MANIFEST_FILE}`);
console.log('');
}
main();