🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

agents-opencode

Package Overview
Dependencies
Maintainers
1
Versions
19
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

agents-opencode - npm Package Compare versions

Comparing version
2.0.0
to
2.0.1
+223
scripts/lib/config-mutator.js
'use strict';
const fs = require('fs');
const path = require('path');
const { readJsonFile, writeJsonFile, isObject } = require('./file-ops.js');
function mergeInstallerConfig(targetConfigPath, sourceConfig, onBeforeWrite, logWarning) {
const patch = {
createdFile: false,
addedPermissionKeys: [],
addedPluginEntries: [],
createdSchema: false,
skipped: false,
changed: false,
};
const sourceConfigForInstall = Object.assign({}, (sourceConfig || {}));
delete sourceConfigForInstall.instructions;
if (!fs.existsSync(targetConfigPath)) {
writeJsonFile(targetConfigPath, sourceConfigForInstall);
patch.createdFile = true;
patch.changed = true;
if (isObject(sourceConfigForInstall.permission)) {
patch.addedPermissionKeys = Object.keys(sourceConfigForInstall.permission);
}
if (Array.isArray(sourceConfigForInstall.plugin)) {
patch.addedPluginEntries = sourceConfigForInstall.plugin.slice();
}
patch.createdSchema = !!sourceConfigForInstall.$schema;
return patch;
}
const existing = readJsonFile(targetConfigPath, `existing config at ${targetConfigPath}`, logWarning);
if (!existing || !isObject(existing)) {
if (logWarning) {
logWarning(`Skipping config merge for ${targetConfigPath} because existing config is invalid JSON.`);
}
patch.skipped = true;
return patch;
}
if (isObject(sourceConfigForInstall.permission)) {
if (existing.permission === undefined) {
existing.permission = {};
}
if (!isObject(existing.permission)) {
if (logWarning) {
logWarning(`Skipping permission merge for ${targetConfigPath}; existing permission is not an object.`);
}
} else {
for (const key of Object.keys(sourceConfigForInstall.permission)) {
if (!(key in existing.permission)) {
existing.permission[key] = sourceConfigForInstall.permission[key];
patch.addedPermissionKeys.push(key);
patch.changed = true;
}
}
}
}
if (Array.isArray(sourceConfigForInstall.plugin)) {
if (!Array.isArray(existing.plugin)) {
existing.plugin = [];
}
for (const entry of sourceConfigForInstall.plugin) {
if (!existing.plugin.includes(entry)) {
existing.plugin.push(entry);
patch.addedPluginEntries.push(entry);
patch.changed = true;
}
}
}
if (patch.changed) {
if (typeof onBeforeWrite === 'function') {
onBeforeWrite();
}
writeJsonFile(targetConfigPath, existing);
}
return patch;
}
function mergeConfigPatches(existingPatch, currentPatch) {
const base = {
createdFile: false,
addedPermissionKeys: [],
createdSchema: false,
skipped: false,
changed: false,
};
const prior = isObject(existingPatch) ? existingPatch : {};
const next = isObject(currentPatch) ? currentPatch : {};
const permissionKeys = new Set([
...(Array.isArray(prior.addedPermissionKeys) ? prior.addedPermissionKeys : []),
...(Array.isArray(next.addedPermissionKeys) ? next.addedPermissionKeys : []),
]);
return {
...base,
...prior,
...next,
createdFile: Boolean(prior.createdFile || next.createdFile),
addedPermissionKeys: [...permissionKeys],
createdSchema: Boolean(prior.createdSchema || next.createdSchema || prior.addedSchema || next.addedSchema),
skipped: Boolean(prior.skipped || next.skipped),
changed: Boolean(prior.changed || next.changed),
};
}
function loadSourceConfig(sourceDir, logWarning) {
const sourceConfigPath = path.join(sourceDir, 'opencode.json');
const sourceConfig = readJsonFile(sourceConfigPath, 'package opencode.json', logWarning);
if (!sourceConfig || !isObject(sourceConfig)) {
throw new Error('Could not parse package opencode.json.');
}
return sourceConfig;
}
function configLooksManaged(configPath, sourceConfig, logWarning) {
if (!fs.existsSync(configPath)) {
return false;
}
const config = readJsonFile(configPath, `config at ${configPath}`, logWarning);
if (!config || !isObject(config)) {
return false;
}
if (isObject(sourceConfig.permission) && isObject(config.permission)) {
for (const key of Object.keys(sourceConfig.permission)) {
if (!(key in config.permission)) {
continue;
}
if (JSON.stringify(config.permission[key]) === JSON.stringify(sourceConfig.permission[key])) {
return true;
}
}
}
return false;
}
function manifestlessCleanup(configPath, sourceConfig, onBeforeMutate, logWarning) {
if (!fs.existsSync(configPath)) {
return { changed: false, removedFile: false };
}
const existing = readJsonFile(configPath, `existing config at ${configPath}`, logWarning);
if (!existing || !isObject(existing)) {
if (logWarning) {
logWarning(`Skipping manifestless config cleanup; invalid JSON at ${configPath}.`);
}
return { changed: false, removedFile: false };
}
if (JSON.stringify(existing) === JSON.stringify(sourceConfig)) {
if (typeof onBeforeMutate === 'function') {
onBeforeMutate();
}
fs.unlinkSync(configPath);
return { changed: true, removedFile: true };
}
let changed = false;
if (isObject(sourceConfig.permission) && isObject(existing.permission)) {
for (const key of Object.keys(sourceConfig.permission)) {
if (!(key in existing.permission)) {
continue;
}
if (JSON.stringify(existing.permission[key]) === JSON.stringify(sourceConfig.permission[key])) {
delete existing.permission[key];
changed = true;
}
}
if (Object.keys(existing.permission).length === 0) {
delete existing.permission;
changed = true;
}
}
if (changed) {
if (typeof onBeforeMutate === 'function') {
onBeforeMutate();
}
writeJsonFile(configPath, existing);
}
return { changed, removedFile: false };
}
function checkLegacyAgentDir(opencodeDir, opts) {
const logError = opts.error;
const pkgName = opts.PACKAGE_NAME;
const AGENT_DIR_LEGACY = opts.AGENT_DIR_LEGACY;
const AGENT_DIR = opts.AGENT_DIR;
const legacyDir = path.join(opencodeDir, AGENT_DIR_LEGACY);
if (fs.existsSync(legacyDir)) {
if (logError) {
logError(`Legacy '.opencode/${AGENT_DIR_LEGACY}/' directory detected.`);
logError(`v2.0.0+ only supports '.opencode/${AGENT_DIR}/' (plural).`);
logError('');
logError(`Migration: mv ${legacyDir} ${path.join(opencodeDir, AGENT_DIR)}`);
logError(`Then re-run: npx ${pkgName}`);
}
return false;
}
return true;
}
module.exports = {
mergeInstallerConfig,
mergeConfigPatches,
loadSourceConfig,
configLooksManaged,
manifestlessCleanup,
checkLegacyAgentDir,
};
'use strict';
const fs = require('fs');
const path = require('path');
const { toManagedPath } = require('./paths.js');
const BACKUP_DIR = '.backups';
const LANGUAGE_MAP = {
dotnet: 'dotnet-clean-architecture.instructions.md',
python: 'python-best-practices.instructions.md',
typescript: 'typescript-strict.instructions.md',
flutter: 'flutter.instructions.md',
go: 'go.instructions.md',
java: 'java-spring-boot.instructions.md',
node: 'node-express.instructions.md',
react: 'react-next.instructions.md',
ruby: 'ruby-on-rails.instructions.md',
rust: 'rust.instructions.md',
sql: 'sql-migrations.instructions.md',
cicd: 'ci-cd-hygiene.instructions.md',
};
const LANGUAGE_INSTRUCTIONS = new Set(Object.values(LANGUAGE_MAP));
const ALWAYS_KEEP = [
'blogger.instructions.md',
'brutal-critic.instructions.md',
'ci-cd-hygiene.instructions.md',
];
function isObject(value) {
return value !== null && typeof value === 'object' && !Array.isArray(value);
}
function ensureDir(dirPath) {
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
}
function readJsonFile(filePath, labelForError, logWarning) {
try {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
} catch (err) {
if (logWarning) {
logWarning(`Could not parse ${labelForError}: ${err.message}`);
}
return null;
}
}
function writeJsonFile(filePath, data) {
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n');
}
function removeIfExists(filePath) {
if (!fs.existsSync(filePath)) {
return false;
}
fs.unlinkSync(filePath);
return true;
}
function removeManagedFile(absolutePath, relativePathFromRoot, paths, backupSession) {
if (!fs.existsSync(absolutePath)) {
return { removed: false, directory: null };
}
backupSession.backupFile(absolutePath, relativePathFromRoot || path.relative(paths.rootDir, absolutePath));
fs.unlinkSync(absolutePath);
return { removed: true, directory: path.dirname(absolutePath) };
}
function listFilesRecursive(rootDir) {
const files = [];
function walk(currentDir, relativeBase) {
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
for (const entry of entries) {
const relativePath = relativeBase ? path.join(relativeBase, entry.name) : entry.name;
const absolutePath = path.join(currentDir, entry.name);
if (entry.isDirectory()) {
walk(absolutePath, relativePath);
} else if (entry.isFile()) {
files.push(relativePath);
}
}
}
walk(rootDir, '');
return files;
}
function getManagedSourceFiles(sourceOpencodeDir) {
const allFiles = listFilesRecursive(sourceOpencodeDir);
return allFiles.filter((relativePath) => {
if (relativePath.includes(`node_modules${path.sep}`) || relativePath === 'node_modules') {
return false;
}
if (relativePath === BACKUP_DIR || relativePath.startsWith(`${BACKUP_DIR}${path.sep}`)) {
return false;
}
if (/\.backup\./.test(relativePath)) {
return false;
}
return true;
});
}
function filesEqual(pathA, pathB) {
try {
const statA = fs.statSync(pathA);
const statB = fs.statSync(pathB);
if (statA.size !== statB.size) {
return false;
}
const contentA = fs.readFileSync(pathA);
const contentB = fs.readFileSync(pathB);
return contentA.equals(contentB);
} catch {
return false;
}
}
function buildManagedFilesFromSource(scope, sourceFiles, paths) {
const managedFiles = [];
for (const relativeFile of sourceFiles) {
const managedPath = toManagedPath(scope, relativeFile);
const absoluteManagedPath = path.join(paths.rootDir, managedPath);
if (fs.existsSync(absoluteManagedPath)) {
managedFiles.push(managedPath);
}
}
return managedFiles;
}
function installManagedTree(sourceOpencodeDir, sourceFiles, destinationOpencodeDir, scope, backupSession, logWarning) {
ensureDir(destinationOpencodeDir);
let copiedCount = 0;
let skippedCount = 0;
let backupCount = 0;
for (const relativeFile of sourceFiles) {
const src = path.join(sourceOpencodeDir, relativeFile);
const dest = path.join(destinationOpencodeDir, relativeFile);
const destParent = path.dirname(dest);
ensureDir(destParent);
if (fs.existsSync(dest)) {
if (filesEqual(src, dest)) {
skippedCount += 1;
continue;
}
try {
if (backupSession && backupSession.backupFile(dest, toManagedPath(scope, relativeFile))) {
backupCount += 1;
}
} catch (err) {
if (logWarning) {
logWarning(`Could not back up existing file ${dest}: ${err.message}`);
}
}
}
fs.copyFileSync(src, dest);
copiedCount += 1;
}
return {
copiedCount,
skippedCount,
backupCount,
};
}
function filterLanguages(installDir, languages, logFns) {
const logWarning = logFns && logFns.warning;
const logInfo = logFns && logFns.info;
const logSuccess = logFns && logFns.success;
const instructionsDir = path.join(installDir, 'instructions');
if (!fs.existsSync(instructionsDir)) {
if (logWarning) logWarning('No instructions directory found — skipping language filter.');
return;
}
const validLanguages = Object.keys(LANGUAGE_MAP);
const requested = languages.split(',').map(function (l) { return l.trim().toLowerCase(); }).filter(Boolean);
const invalid = requested.filter(function (l) { return !validLanguages.includes(l); });
if (invalid.length > 0) {
if (logWarning) logWarning(`Unknown language(s): ${invalid.join(', ')}`);
if (logInfo) logInfo(`Available: ${validLanguages.join(', ')}`);
}
const valid = requested.filter(function (l) { return validLanguages.includes(l); });
if (valid.length === 0) {
if (logWarning) logWarning('No valid languages specified — keeping all instruction files.');
return;
}
const keepFiles = new Set(ALWAYS_KEEP);
for (var i = 0; i < valid.length; i++) {
if (LANGUAGE_MAP[valid[i]]) {
keepFiles.add(LANGUAGE_MAP[valid[i]]);
}
}
const allFiles = fs.readdirSync(instructionsDir);
const removed = [];
for (var j = 0; j < allFiles.length; j++) {
var file = allFiles[j];
if (keepFiles.has(file) || !LANGUAGE_INSTRUCTIONS.has(file)) {
continue;
}
try {
fs.unlinkSync(path.join(instructionsDir, file));
removed.push(file);
} catch (err) {
if (logWarning) logWarning(`Could not remove ${file}: ${err.message}`);
}
}
if (logSuccess) logSuccess(`✓ Applied language filter: ${valid.join(', ')}`);
if (removed.length > 0) {
if (logInfo) logInfo(`Removed ${removed.length} instruction file(s): ${removed.join(', ')}`);
}
}
function pruneEmptyDirectories(directories, stopAtDirectory) {
const sorted = Array.from(directories).sort(function (a, b) { return b.length - a.length; });
let prunedCount = 0;
for (var i = 0; i < sorted.length; i++) {
var dirPath = sorted[i];
if (!fs.existsSync(dirPath)) {
continue;
}
if (path.resolve(dirPath) === path.resolve(stopAtDirectory)) {
continue;
}
try {
const entries = fs.readdirSync(dirPath);
if (entries.length === 0) {
fs.rmdirSync(dirPath);
prunedCount += 1;
}
} catch {
// ignore pruning errors
}
}
return prunedCount;
}
module.exports = {
isObject,
ensureDir,
readJsonFile,
writeJsonFile,
removeIfExists,
removeManagedFile,
listFilesRecursive,
getManagedSourceFiles,
filesEqual,
buildManagedFilesFromSource,
installManagedTree,
filterLanguages,
pruneEmptyDirectories,
};
'use strict';
const os = require('os');
const path = require('path');
const fs = require('fs');
const MANIFEST_FILE = '.agents-opencode-manifest.json';
const VERSION_FILE = '.opencode-agents-version';
const AGENT_DIR = 'agents';
function getHomeDir() {
return os.homedir();
}
function getGlobalConfigDir() {
return path.join(getHomeDir(), '.config', 'opencode');
}
function getScopePaths(scope, projectDir) {
if (scope === 'global') {
const rootDir = getGlobalConfigDir();
return {
scope,
rootDir,
opencodeDir: rootDir,
manifestPath: path.join(rootDir, MANIFEST_FILE),
versionPath: path.join(rootDir, VERSION_FILE),
configPath: path.join(rootDir, 'opencode.json'),
agentsMdPath: null,
};
}
const resolvedProjectDir = path.resolve(projectDir || process.cwd());
return {
scope,
rootDir: resolvedProjectDir,
opencodeDir: path.join(resolvedProjectDir, '.opencode'),
manifestPath: path.join(resolvedProjectDir, '.opencode', MANIFEST_FILE),
versionPath: path.join(resolvedProjectDir, VERSION_FILE),
configPath: path.join(resolvedProjectDir, 'opencode.json'),
agentsMdPath: path.join(resolvedProjectDir, 'AGENTS.md'),
};
}
function toManagedPath(scope, relativeOpencodeFile) {
if (scope === 'global') {
return relativeOpencodeFile;
}
return path.join('.opencode', relativeOpencodeFile);
}
function resolveAgentDirectory(opencodeDir) {
const agentDir = path.join(opencodeDir, AGENT_DIR);
if (fs.existsSync(agentDir)) {
return agentDir;
}
return null;
}
module.exports = {
MANIFEST_FILE,
VERSION_FILE,
AGENT_DIR,
getHomeDir,
getGlobalConfigDir,
getScopePaths,
toManagedPath,
resolveAgentDirectory,
};
+4
-2
{
"name": "agents-opencode",
"version": "2.0.0",
"version": "2.0.1",
"description": "OpenCode Agents: Intelligent AI assistants for software development. Features 9 specialized agents (including legal-advisor for license auditing and compliance), 14 coding standards, automated code review, documentation generation, OpenCode plugin compatibility, and cross-platform installation. Supports .NET, Python, TypeScript, Flutter, Go, Java, Node.js, React, Ruby, and Rust with plan-first execution and context-aware assistance.",

@@ -8,3 +8,4 @@ "files": [

".opencode",
"opencode.json"
"opencode.json",
"scripts/lib"
],

@@ -36,2 +37,3 @@ "repository": {

"validate:context": "node scripts/check-context-size.js",
"validate:npx": "node scripts/test-npx-integrity.js",
"validate:all": "npm run doctor"

@@ -38,0 +40,0 @@ },