Files
ECC/scripts/control-pane.js
T
wakqasahmedandhaelyra ce11e8f690 fix(install): ship ajv/sql.js with the plugin install bundle (#2822)
install-plan.js and install-apply.js both require ./lib/install/config at
load time, and that module required ajv unconditionally at the top of the
file even though ajv is only actually used when validating an
ecc-install.json. When ECC is installed via the Claude Code plugin
marketplace, the marketplace directory is a bare git clone with no
node_modules, so requiring ajv crashes commands like --list-profiles that
never touch install-config validation at all.

Same root cause in scripts/lib/control-pane/state.js: sql.js and
@iarna/toml were required at module scope even though they are only used
inside openSqlDatabase() and readTomlConfig(), so control-pane.js --help
crashed too.

Make both requires lazy so they only load when the feature that actually
needs them runs. For the case where ajv/sql.js/js-yaml/@iarna-toml is
genuinely needed and still missing, add a small helper that turns the raw
MODULE_NOT_FOUND into an actionable message naming the package and the
install command, instead of a stack trace (install-apply.js) or, worse, an
unhandled crash with a usage banner tacked on that reads like a bad
argument (install-plan.js, control-pane.js). Applied the same helper to
memory-mcp.mjs, where ajv is genuinely load-bearing (it compiles every MCP
tool's JSON schema up front) so it can't be made lazy the same way.

Added a regression test that copies just scripts/, schemas/, and
manifests/ into a directory with no node_modules anywhere above it in the
filesystem, which reproduces the plugin-marketplace install exactly, and
asserts install-plan.js and control-pane.js still work.
2026-09-07 16:26:10 -04:00

68 lines
1.5 KiB
JavaScript
Executable File

#!/usr/bin/env node
'use strict';
const { spawn } = require('child_process');
const {
createControlPaneServer,
parseArgs,
usage,
} = require('./lib/control-pane/server');
const { describeMissingDependencyError } = require('./lib/missing-dependency');
function openBrowser(url) {
if (process.platform !== 'darwin') return;
const child = spawn('open', [url], {
stdio: 'ignore',
detached: true,
});
child.on('error', error => {
console.error(`[control-pane] failed to open browser: ${error.message}`);
});
child.unref();
}
async function main(argv = process.argv) {
const args = parseArgs(argv);
if (args.help) {
console.log(usage());
return;
}
const app = createControlPaneServer(args);
await app.listen();
console.log(`ECC Control Pane: ${app.url}`);
console.log(`ECC2 database: ${app.config.dbPath}`);
console.log(`ECC state database: ${app.config.stateDbPath}`);
console.log(args.allowActions ? 'Actions: enabled for local allowlist' : 'Actions: read-only');
if (args.openBrowser) {
openBrowser(app.url);
}
const shutdown = async () => {
try {
await app.close();
} finally {
process.exit(0);
}
};
process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);
}
if (require.main === module) {
main().catch(error => {
console.error(`[control-pane] ${describeMissingDependencyError(error) || error.message}`);
process.exit(1);
});
}
module.exports = {
main,
openBrowser,
};