mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-28 12:35:13 +02:00
193 lines
10 KiB
JavaScript
193 lines
10 KiB
JavaScript
#!/usr/bin/env node
|
|
'use strict';
|
|
|
|
const path = require('path');
|
|
|
|
const ROOT = path.resolve(__dirname, '..');
|
|
const PROFILE_IDS = Object.freeze(['lean@1', 'full@1']);
|
|
const COMMANDS = Object.freeze(['show', 'preview', 'explain', 'carrier']);
|
|
const VALUES = Object.freeze(['--target', '--selection', '--include', '--exclude']);
|
|
|
|
function helpText() {
|
|
return `ECC context profiles (read-only preview)
|
|
|
|
Usage:
|
|
ecc profile show [lean@1|full@1] [--json]
|
|
ecc profile preview [lean@1|full@1] [--target codex] [--selection auto|manual|suggest]
|
|
[--include skill:<id>] [--exclude skill:<id>] [--json]
|
|
ecc profile explain skill:<id> [--target codex] [--json]
|
|
ecc profile carrier [lean@1|full@1] [--target codex] [--selection auto|manual|suggest]
|
|
[--include skill:<id>] [--exclude skill:<id>] [--json]
|
|
|
|
Include/exclude flags may be repeated. Preview defaults: lean@1, codex, auto.
|
|
These defaults describe a proposal, not your installed configuration.
|
|
Show/preview/explain/carrier are read-only and do not activate a provider or grant authority.
|
|
Carrier lists proposed files only; it accepts no destination and writes no artifact.
|
|
Token estimates cover skill metadata only; actual host context remains unobserved.
|
|
The existing install --profile and hook profile flags keep their own meanings.
|
|
|
|
Experimental managed profiles and bounded task context:
|
|
ecc profile resolve [lean|full] --task-input task.json|- [--load] [--previous receipt.json] [--json]
|
|
ecc profile run --task-input task.json|- [--state-root <directory>] [--target codex|claude] [--dry-run] [--json]
|
|
ecc profile set lean|full --state-root <dedicated-directory> [--selection auto|manual|suggest]
|
|
[--target codex] [--include skill:<id>] [--exclude skill:<id>] [--dry-run] [--json]
|
|
ecc profile status --state-root <directory> [--json]
|
|
ecc profile mode auto|manual|suggest --state-root <directory> [--dry-run] [--json]
|
|
ecc profile rollback --state-root <directory> [--expected-revision N] [--json]
|
|
ecc profile recover --state-root <directory> [--json]
|
|
ecc profile start --state-root <directory> --native-root <directory> [--dry-run]
|
|
ecc profile prepare-native --state-root <directory> --native-root <dedicated-directory> [--dry-run] [--json]
|
|
ecc profile native-status --state-root <directory> --native-root <directory> [--json]
|
|
ecc profile native-rollback --state-root <directory> --native-root <directory> [--json]
|
|
ecc profile native-recover --state-root <directory> --native-root <directory> [--json]
|
|
Task input may be one bounded UTF-8 JSON object on stdin with --task-input - (65536 bytes maximum).
|
|
Start requires prepare-native, launches the pinned native TUI with inherited stdio and provider permissions,
|
|
and reads a receipt-bound isolated AGENTS bootstrap. Authenticate separately in the isolated home; credentials are never copied.
|
|
Set stages owned generations; provider discovery is verified separately.
|
|
Resolve returns context only with --load; suggest and --dry-run never return skill bodies.
|
|
Use resolve --state-root <directory> to honor the saved base, mode and exclusions.
|
|
Run with --native-root to use a verified isolated Codex generation. Existing sessions are unchanged.
|
|
`;
|
|
}
|
|
|
|
function parseArgs(argv) {
|
|
const parsed = { command: null, id: null, target: 'codex', selectionMode: 'auto',
|
|
include: [], exclude: [], json: false, help: false };
|
|
const seen = new Set();
|
|
const args = argv.filter(arg => arg !== '--dry-run');
|
|
if (!args.length) return { ...parsed, help: true };
|
|
if (!args[0].startsWith('-')) parsed.command = args.shift();
|
|
if (parsed.command && !COMMANDS.includes(parsed.command)) {
|
|
throw new Error(`Unknown read-only profile command: ${parsed.command}`);
|
|
}
|
|
for (let index = 0; index < args.length; index++) {
|
|
const arg = args[index];
|
|
if (['--help', '-h'].includes(arg)) parsed.help = true;
|
|
else if (arg === '--json') parsed.json = true;
|
|
else if (VALUES.includes(arg)) {
|
|
const value = args[++index];
|
|
if (!value || value.startsWith('-')) throw new Error(`Missing value for ${arg}`);
|
|
if (seen.has(arg) && !['--include', '--exclude'].includes(arg)) {
|
|
throw new Error(`Duplicate argument: ${arg}`);
|
|
}
|
|
seen.add(arg);
|
|
if (arg === '--include') parsed.include.push(value);
|
|
if (arg === '--exclude') parsed.exclude.push(value);
|
|
if (arg === '--target') parsed.target = value;
|
|
if (arg === '--selection') parsed.selectionMode = value;
|
|
} else if (!arg.startsWith('-') && !parsed.id) parsed.id = arg;
|
|
else throw new Error(`Unknown argument: ${arg}`);
|
|
}
|
|
if (parsed.help) return parsed;
|
|
if (!parsed.command) throw new Error('Choose show, preview, explain, or carrier');
|
|
const allowed = ['preview', 'carrier'].includes(parsed.command) ? VALUES
|
|
: parsed.command === 'explain' ? ['--target'] : [];
|
|
for (const flag of seen) {
|
|
if (!allowed.includes(flag)) throw new Error(`${flag} is unavailable for ${parsed.command}`);
|
|
}
|
|
if (parsed.command === 'explain' && !parsed.id) throw new Error('Missing skill ID for explain');
|
|
return parsed;
|
|
}
|
|
|
|
function envelope(status, summary, values = {}) {
|
|
return { schemaVersion: 'ecc.profile-inspection.v1', status, summary,
|
|
activation: 'unobserved', next_actions: [], artifacts: [], ...values };
|
|
}
|
|
|
|
function buildResponse(options, repoRoot = ROOT) {
|
|
const { loadContextProfile, compileContextProfile } = require('./lib/context-profiles');
|
|
const { explainContextEntry } = require('./lib/context-pack-registry');
|
|
if (options.command === 'show') {
|
|
const values = options.id
|
|
? { profile: loadContextProfile(options.id, { repoRoot }) }
|
|
: { profiles: PROFILE_IDS.map(id => loadContextProfile(id, { repoRoot })) };
|
|
return envelope('success', 'Context profile definitions; installed state is unobserved.', values);
|
|
}
|
|
if (options.command === 'explain') {
|
|
return envelope('success', 'Exact catalog entry; no skill has been loaded or invoked.', {
|
|
entry: explainContextEntry({ repoRoot, id: options.id, target: options.target }),
|
|
});
|
|
}
|
|
if (options.command === 'carrier') {
|
|
const { planContextCarrier } = require('./lib/context-carriers');
|
|
const carrier = planContextCarrier({ repoRoot, profileId: options.id || 'lean@1',
|
|
target: options.target, selectionMode: options.selectionMode,
|
|
include: options.include, exclude: options.exclude });
|
|
return envelope('warning', 'Proposed skill-only carrier; no files written and native discovery remains unobserved.', {
|
|
carrier, artifacts: [{ kind: 'context-carrier', digest: carrier.carrierDigest }],
|
|
next_actions: [carrier.status === 'unsupported'
|
|
? 'This target has no carrier layout yet. Choose an implemented target or add a tested adapter.'
|
|
: 'Review file mappings and collect disposable fixture and native discovery evidence before activation.'],
|
|
});
|
|
}
|
|
const plan = compileContextProfile({ repoRoot, profileId: options.id || 'lean@1',
|
|
target: options.target, selectionMode: options.selectionMode,
|
|
include: options.include, exclude: options.exclude });
|
|
return envelope('warning', 'Proposed skill-discovery projection; runtime activation and whole-context cost are unobserved.', {
|
|
plan, artifacts: [{ kind: 'context-plan', digest: plan.planDigest }],
|
|
next_actions: ['Review selected IDs, exclusions, and target declarations before adapter integration.'],
|
|
});
|
|
}
|
|
|
|
function formatText(response) {
|
|
const lines = [response.summary, `Activation: ${response.activation}`];
|
|
if (response.profiles) lines.push(...response.profiles.map(profile => `${profile.id}: ${profile.description}`));
|
|
if (response.profile) lines.push(JSON.stringify(response.profile, null, 2));
|
|
if (response.entry) {
|
|
const entry = response.entry;
|
|
lines.push(`${entry.id}: ${entry.description}`, `Source: ${entry.sourcePath}`,
|
|
`Install support: ${entry.projection.installSupport}; native support: ${entry.projection.nativeSupport}`);
|
|
}
|
|
if (response.plan) {
|
|
const plan = response.plan;
|
|
lines.push(`Profile: ${plan.profileId}; selection: ${plan.selectionMode}; target: ${plan.target}`,
|
|
`Selected: ${plan.selectedIds.join(', ') || '(none)'}`,
|
|
`Routed: ${plan.routedIds.length}; excluded: ${plan.excludedIds.length}`,
|
|
`Metadata estimate: ${plan.estimate.estimatedTokens} tokens (${plan.estimate.method}).`,
|
|
'Whole ECC startup budget: unobserved; this estimate does not certify a native host.',
|
|
`Plan digest: ${plan.planDigest}`, ...plan.limitations);
|
|
}
|
|
if (response.carrier) {
|
|
const carrier = response.carrier;
|
|
lines.push(`Carrier: ${carrier.status}; profile: ${carrier.profileId}; target: ${carrier.target}`,
|
|
`Selected: ${carrier.selectedIds.length}; routed: ${carrier.routedIds.length}; excluded: ${carrier.excludedIds.length}`,
|
|
`Proposed files: ${carrier.files.length}; native discovery: ${carrier.nativeSupport}`,
|
|
`Carrier digest: ${carrier.carrierDigest}`, ...carrier.limitations);
|
|
}
|
|
lines.push(...response.next_actions.map(action => `Next: ${action}`));
|
|
const text = `${lines.join('\n')}\n`;
|
|
return [...text].map(character => {
|
|
const code = character.codePointAt(0);
|
|
return ((code < 32 && code !== 9 && code !== 10) || (code >= 127 && code <= 159))
|
|
? `\\u${code.toString(16).padStart(4, '0')}` : character;
|
|
}).join('');
|
|
}
|
|
|
|
function main(argv = process.argv.slice(2)) {
|
|
try {
|
|
const operations = require('./lib/context-profile-commands');
|
|
if (operations.COMMANDS.includes(argv.find(arg => arg !== '--dry-run'))) {
|
|
const response = operations.run(argv);
|
|
process.stdout.write(argv.includes('--json') ? `${JSON.stringify(response, null, 2)}\n`
|
|
: formatText(response) + `${JSON.stringify(response.selection || response.store || response.launch || response.native || response.interactive, null, 2)}\n`);
|
|
if (response.interactive?.status === 'failed') return response.interactive.exitCode || 1;
|
|
return response.status === 'error' ? 1 : 0;
|
|
}
|
|
const options = parseArgs(argv);
|
|
if (options.help) { process.stdout.write(helpText()); return 0; }
|
|
const response = buildResponse(options);
|
|
process.stdout.write(options.json ? `${JSON.stringify(response, null, 2)}\n` : formatText(response));
|
|
return 0;
|
|
} catch (error) {
|
|
const response = envelope('error', error.message, {
|
|
next_actions: ['Run ecc profile --help and correct the request or source contract. No activation was attempted.'],
|
|
});
|
|
if (argv.includes('--json')) process.stdout.write(`${JSON.stringify(response, null, 2)}\n`);
|
|
else process.stderr.write(formatText(response));
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (require.main === module) process.exitCode = main();
|
|
module.exports = { buildResponse, formatText, helpText, main, parseArgs };
|