#!/usr/bin/env node const fs = require('node:fs'); const path = require('node:path'); const { dispatchTask, parseArgs, readConfig, summarizeCliResult, } = require('../tools/erp_task_dispatcher'); const { validateTaskEnvelope } = require('../tools/erp_task_contract'); function resolvePath(filePath, cwd = process.cwd()) { if (!filePath) return ''; return path.isAbsolute(filePath) ? filePath : path.join(cwd, filePath); } function readTaskFile(taskPath) { const resolved = resolvePath(taskPath); const text = fs.readFileSync(resolved, 'utf8').replace(/^\uFEFF/, ''); return JSON.parse(text); } function validationResult(validation, expectedOperation = '') { const task = validation.task || {}; const operation = task.operation || (validation.envelope && validation.envelope.operation) || null; const route = task.route || (validation.envelope && validation.envelope.route) || null; const violations = [...(validation.violations || [])]; if (expectedOperation && operation && operation !== expectedOperation) { violations.push(`expected operation ${expectedOperation}, received ${operation}`); } const status = violations.length ? 'invalid_task' : (validation.status || 'invalid_task'); return { status, reason: status === 'needs_clarification' ? 'adapter_not_ready' : 'invalid_task', operation, route, violations, customerMessage: status === 'needs_clarification' ? 'Task needs clarification before ERP execution.' : 'The ERP task JSON is invalid and was not sent to an executor.', }; } async function runTaskFile(taskPath, options = {}) { const input = readTaskFile(taskPath); const validation = validateTaskEnvelope(input); if (!validation.valid) return validationResult(validation, options.expectedOperation); const expectedOperation = options.expectedOperation || ''; if (expectedOperation && validation.task.operation !== expectedOperation) { return validationResult(validation, expectedOperation); } const result = await dispatchTask(validation.envelope, { mode: options.mode || 'dry-run', config: options.config || {}, auditDir: options.auditDir, source: options.source, sender: options.sender, senderId: options.senderId, senderName: options.senderName, chatId: options.chatId, channelId: options.channelId, handlers: options.handlers, registryPath: options.registryPath, taskLock: options.taskLock, }); return summarizeCliResult(result); } function helpText() { return [ 'Usage:', ' node scripts/run_erp_task.js --task task.json [--mode dry-run|execute] [--config config.json] [--audit-dir dir] [--json]', '', 'Runs a validated ERP task JSON through the dispatcher. The default mode is dry-run.', ].join('\n'); } function exitCodeForResult(result) { if (!result || result.status === 'blocked' || result.status === 'invalid_task' || result.status === 'needs_clarification') return 2; return 0; } async function main(argv = process.argv.slice(2)) { const args = parseArgs(argv); if (args.help || !args.task) { console.log(helpText()); process.exitCode = args.help ? 0 : 1; return; } const result = await runTaskFile(args.task, { mode: args.mode, config: readConfig(args.config), auditDir: args.auditDir ? resolvePath(args.auditDir) : undefined, source: { sender: args.sender, senderId: args.senderId, senderName: args.senderName, chatId: args.chatId, channelId: args.channelId, }, }); if (args.json) { console.log(JSON.stringify(result, null, 2)); } else { console.log(result.customerMessage || result.status); if (result.auditPath) console.log(`audit: ${result.auditPath}`); if (Array.isArray(result.violations) && result.violations.length) { console.log(`violations: ${result.violations.join('; ')}`); } } process.exitCode = exitCodeForResult(result); } if (require.main === module) { main().catch((error) => { console.error(error.stack || error.message); process.exitCode = 1; }); } module.exports = { resolvePath, readTaskFile, validationResult, runTaskFile, exitCodeForResult, main, };