mirror of
https://github.com/affaan-m/ECC.git
synced 2026-08-21 23:12:24 +02:00
* feat(install): add guided Claude plugin setup * fix: support Claude command shims on Windows * feat: support safe Claude plugin scope migration * fix(install): preserve interactive setup terminal * fix(install): auto-migrate setup scope changes * feat(install): add guided multi-harness installer * fix(install): sync Yarn binary metadata * fix(install): handle wizard EOF on Node 18 * ci: allow installer matrix tests to finish * test(install): allow slower PowerShell delegation * fix(install): harden guided provider reconciliation * test(install): harden packaged and local compatibility * chore: prepare guided installer release 2.2.0 * fix(install): report refreshed Codex marketplace state * fix(install): verify managed content provenance * test(install): allow empty Yarn smoke fixture * test(install): invoke Windows package shims safely * fix(install): close cross-platform release gaps * fix(install): require trusted GitHub origins * fix(install): preserve hook profile precedence * refactor(install): centralize trusted GitHub origins * ci: retrigger workflow run after merge of main Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
155 lines
4.4 KiB
JavaScript
155 lines
4.4 KiB
JavaScript
#!/usr/bin/env node
|
|
'use strict';
|
|
|
|
/**
|
|
* Stateful Claude plugin CLI fake.
|
|
*
|
|
* Environment:
|
|
* - ECC_TEST_CLAUDE_STATE: JSON state file (required)
|
|
* - ECC_TEST_CLAUDE_CALLS: JSONL argv log (optional)
|
|
*
|
|
* State supports:
|
|
* {
|
|
* plugins: [{ id, scope, enabled }],
|
|
* marketplaces: [{ name, source, repo, scope }],
|
|
* pluginListResponses: [array | string],
|
|
* marketplaceListResponses: [array | string],
|
|
* failures: [{ argv: [...], status, stderr, times }]
|
|
* }
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
|
|
const args = process.argv.slice(2);
|
|
const statePath = process.env.ECC_TEST_CLAUDE_STATE;
|
|
const callsPath = process.env.ECC_TEST_CLAUDE_CALLS;
|
|
|
|
if (!statePath) {
|
|
process.stderr.write('ECC_TEST_CLAUDE_STATE is required\n');
|
|
process.exit(2);
|
|
}
|
|
|
|
if (callsPath) {
|
|
fs.appendFileSync(callsPath, `${JSON.stringify(args)}\n`);
|
|
}
|
|
|
|
function readState() {
|
|
return JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
}
|
|
|
|
function writeState(state) {
|
|
fs.writeFileSync(statePath, `${JSON.stringify(state, null, 2)}\n`);
|
|
}
|
|
|
|
function sameArgv(left, right) {
|
|
return (
|
|
Array.isArray(left)
|
|
&& left.length === right.length
|
|
&& left.every((value, index) => value === right[index])
|
|
);
|
|
}
|
|
|
|
function shiftResponse(state, key, fallback) {
|
|
const queue = Array.isArray(state[key]) ? [...state[key]] : [];
|
|
if (queue.length === 0) return fallback;
|
|
const response = queue.shift();
|
|
writeState({ ...state, [key]: queue });
|
|
return response;
|
|
}
|
|
|
|
function printJsonResponse(response) {
|
|
process.stdout.write(typeof response === 'string' ? response : JSON.stringify(response));
|
|
}
|
|
|
|
let state = readState();
|
|
const failureIndex = (state.failures || []).findIndex(rule => (
|
|
sameArgv(rule.argv, args) && (rule.times === undefined || rule.times > 0)
|
|
));
|
|
|
|
if (failureIndex >= 0) {
|
|
const failure = state.failures[failureIndex];
|
|
const nextFailures = state.failures.map((rule, index) => (
|
|
index === failureIndex && Number.isInteger(rule.times)
|
|
? { ...rule, times: Math.max(0, rule.times - 1) }
|
|
: rule
|
|
));
|
|
writeState({ ...state, failures: nextFailures });
|
|
process.stderr.write(failure.stderr || 'injected Claude CLI failure\n');
|
|
process.exit(Number.isInteger(failure.status) ? failure.status : 1);
|
|
}
|
|
|
|
const joined = args.join(' ');
|
|
|
|
if (joined === 'plugin list --json') {
|
|
printJsonResponse(shiftResponse(state, 'pluginListResponses', state.plugins || []));
|
|
process.exit(0);
|
|
}
|
|
|
|
if (joined === 'plugin marketplace list --json') {
|
|
printJsonResponse(
|
|
shiftResponse(state, 'marketplaceListResponses', state.marketplaces || [])
|
|
);
|
|
process.exit(0);
|
|
}
|
|
|
|
if (args[0] === 'plugin' && args[1] === 'marketplace' && args[2] === 'add') {
|
|
const source = args[3];
|
|
const scopeIndex = args.indexOf('--scope');
|
|
const scope = scopeIndex >= 0 ? args[scopeIndex + 1] : 'user';
|
|
const marketplaces = [
|
|
...(state.marketplaces || []).filter(entry => entry.name !== 'ecc'),
|
|
{
|
|
name: 'ecc',
|
|
source: 'github',
|
|
repo: 'affaan-m/ECC',
|
|
url: source,
|
|
scope,
|
|
},
|
|
];
|
|
writeState({ ...state, marketplaces });
|
|
process.exit(0);
|
|
}
|
|
|
|
if (args[0] === 'plugin' && args[1] === 'marketplace' && args[2] === 'update') {
|
|
process.exit(0);
|
|
}
|
|
|
|
if (args[0] === 'plugin' && args[1] === 'install' && args[2] === 'ecc@ecc') {
|
|
const scopeIndex = args.indexOf('--scope');
|
|
const scope = scopeIndex >= 0 ? args[scopeIndex + 1] : 'user';
|
|
const plugins = [
|
|
...(state.plugins || []).filter(plugin => (
|
|
plugin.id !== 'ecc@ecc' || plugin.scope !== scope
|
|
)),
|
|
{ id: 'ecc@ecc', scope, enabled: true, version: '2.0.0' },
|
|
];
|
|
writeState({ ...state, plugins });
|
|
process.exit(0);
|
|
}
|
|
|
|
if (args[0] === 'plugin' && args[1] === 'update' && args[2] === 'ecc@ecc') {
|
|
const scopeIndex = args.indexOf('--scope');
|
|
const scope = scopeIndex >= 0 ? args[scopeIndex + 1] : 'user';
|
|
const plugins = (state.plugins || []).map(plugin => (
|
|
plugin.id === 'ecc@ecc' && plugin.scope === scope
|
|
? { ...plugin, enabled: true, version: '2.0.0' }
|
|
: plugin
|
|
));
|
|
writeState({ ...state, plugins });
|
|
process.exit(0);
|
|
}
|
|
|
|
if (args[0] === 'plugin' && args[1] === 'uninstall') {
|
|
const pluginId = args[2];
|
|
const scopeIndex = args.indexOf('--scope');
|
|
const scope = scopeIndex >= 0 ? args[scopeIndex + 1] : 'user';
|
|
const plugins = (state.plugins || []).filter(plugin => !(
|
|
plugin.id === pluginId && plugin.scope === scope
|
|
));
|
|
writeState({ ...state, plugins });
|
|
process.exit(0);
|
|
}
|
|
|
|
process.stderr.write(`Unsupported fake Claude invocation: ${JSON.stringify(args)}\n`);
|
|
process.exit(2);
|