UNPKG

qnce-engine

Version:

Core QNCE (Quantum Narrative Convergence Engine) - Framework agnostic narrative engine with performance optimization

191 lines 8.51 kB
#!/usr/bin/env node "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = require("fs"); const path_1 = require("path"); const core_js_1 = require("../telemetry/core.js"); const core_js_2 = require("../engine/core.js"); const CustomJSONAdapter_js_1 = require("../adapters/story/CustomJSONAdapter.js"); const TwisonAdapter_js_1 = require("../adapters/story/TwisonAdapter.js"); const InkAdapter_js_1 = require("../adapters/story/InkAdapter.js"); const validateStoryData_js_1 = require("../schemas/validateStoryData.js"); /** * QNCE Import CLI * Detects story format (Custom JSON, Twison, basic Ink) and outputs normalized QNCE StoryData JSON */ function detectAdapter(payload) { const candidates = [ { key: 'json', inst: new CustomJSONAdapter_js_1.CustomJSONAdapter() }, { key: 'twison', inst: new TwisonAdapter_js_1.TwisonAdapter() }, { key: 'ink', inst: new InkAdapter_js_1.InkAdapter() } ]; for (const c of candidates) { try { if (c.inst.detect?.(payload)) return c; } catch { } } return candidates[0]; } async function readStdin() { const chunks = []; return await new Promise((resolve) => { process.stdin.on('data', (d) => chunks.push(Buffer.from(d))); process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8'))); }); } async function main() { const args = process.argv.slice(2); const showHelp = args.length === 0 && process.stdin.isTTY; if (showHelp || args.includes('--help') || args.includes('-h')) { console.log(`\nQNCE Import CLI\nUsage: qnce-import <input-file>|(read from stdin) [--out <file>|stdout] [--id-prefix <prefix>] [--format json|twison|ink] [--strict] [--experimental-ink] [--telemetry <console|file|none>] [--telemetry-file <path>] [--telemetry-sample <0..1>]\n`); process.exit(0); } const formatIdx = args.indexOf('--format'); const format = formatIdx >= 0 ? args[formatIdx + 1] : undefined; const strict = args.includes('--strict'); const experimentalInk = args.includes('--experimental-ink'); const idPrefixIndex = args.indexOf('--id-prefix'); const idPrefix = idPrefixIndex >= 0 ? args[idPrefixIndex + 1] : ''; const outIndex = args.indexOf('--out'); const outArg = outIndex >= 0 ? args[outIndex + 1] : undefined; // Telemetry flags const telemetryIdx = args.indexOf('--telemetry'); const telemetryKind = telemetryIdx >= 0 ? (args[telemetryIdx + 1] || 'none') : 'none'; const telemetryFileIdx = args.indexOf('--telemetry-file'); const telemetryPath = telemetryFileIdx >= 0 ? args[telemetryFileIdx + 1] : undefined; const telemetrySampleIdx = args.indexOf('--telemetry-sample'); const telemetrySample = telemetrySampleIdx >= 0 ? Math.max(0, Math.min(1, parseFloat(args[telemetrySampleIdx + 1]))) : undefined; let telemetry; if (telemetryKind && telemetryKind !== 'none') { const adapter = telemetryKind === 'file' ? (0, core_js_1.createTelemetryAdapter)('file', { path: telemetryPath || 'qnce-import.ndjson' }) : (0, core_js_1.createTelemetryAdapter)('console'); telemetry = (0, core_js_1.createTelemetry)({ adapter, enabled: true, sampleRate: telemetrySample ?? (process.env.NODE_ENV === 'production' ? 0 : 0.25), defaultCtx: { engineVersion: '1.3.1', env: process.env.NODE_ENV || 'dev', sessionId: `import-${Date.now()}` } }); } let raw; let inputName = 'stdin'; if (args[0] && !args[0].startsWith('--')) { const inPath = (0, path_1.resolve)(args[0]); inputName = inPath; raw = (0, fs_1.readFileSync)(inPath, 'utf-8'); } else { raw = await readStdin(); } let exitCode = 0; try { const t0 = Date.now(); const json = JSON.parse(raw); let selected; if (format) { const map = { json: new CustomJSONAdapter_js_1.CustomJSONAdapter(), twison: new TwisonAdapter_js_1.TwisonAdapter(), ink: new InkAdapter_js_1.InkAdapter() }; if (!map[format]) throw new Error(`Unknown format: ${format}`); selected = { key: format, inst: map[format] }; } else { selected = detectAdapter(json); console.log(`ℹ️ Detected format: ${selected.key} (from ${inputName})`); } const normalized = await selected.inst.load(json, { idPrefix, strict, experimentalInk }); // Schema validation (strict enforces failure) const schema = (0, validateStoryData_js_1.validateStoryData)(normalized); if (!schema.valid) { const msg = `Schema validation failed with ${schema.errors?.length || 0} error(s).`; const fmtErrors = (schema.errors || []).map((e) => ` - ${(e.instancePath ?? e.dataPath) || ''} ${e.message || ''}`).join('\n'); if (strict) { console.error(`❌ ${msg}`); if (fmtErrors) console.error(fmtErrors); process.exit(2); } else { console.warn(`⚠️ ${msg}`); if (fmtErrors) console.warn(fmtErrors); exitCode = Math.max(exitCode, 1); } } // Additional semantic checks: initial node exists, dangling nextNodeId targets const nodeIds = new Set(normalized.nodes.map(n => n.id)); const invalidLinks = []; for (const n of normalized.nodes) { for (const c of n.choices) { if (c.nextNodeId && !nodeIds.has(c.nextNodeId)) { invalidLinks.push({ from: n.id, to: c.nextNodeId }); } } } const initialExists = nodeIds.has(normalized.initialNodeId); if (!initialExists) { const msg = `Initial node '${normalized.initialNodeId}' does not exist in nodes`; if (strict) { console.error(`❌ ${msg}`); process.exit(2); } else { console.warn(`⚠️ ${msg}`); exitCode = Math.max(exitCode, 1); } } if (invalidLinks.length > 0) { const msg = `Found ${invalidLinks.length} dangling link(s)`; const lines = invalidLinks.slice(0, 10).map(l => ` - ${l.from} -> ${l.to} (missing)`); if (strict) { console.error(`❌ ${msg}`); if (lines.length) console.error(lines.join('\n')); process.exit(2); } else { console.warn(`⚠️ ${msg}`); if (lines.length) console.warn(lines.join('\n')); exitCode = Math.max(exitCode, 1); } } const story = (0, core_js_2.loadStoryData)(normalized); // Emit single load event with duration and warnings const durationMs = Date.now() - t0; try { telemetry?.emit({ type: 'import.load', payload: { inputName, format: selected.key, durationMs, warnings: exitCode }, ts: Date.now(), ctx: { sessionId: `import-${t0}`, engineVersion: '1.3.1' } }); } catch { } if (outArg && outArg !== 'stdout') { const outPath = (0, path_1.resolve)(outArg); (0, fs_1.writeFileSync)(outPath, JSON.stringify(story, null, 2)); console.log(`✅ Imported and normalized to ${outPath}`); } else if (!outArg && inputName !== 'stdin') { const outPath = (0, path_1.resolve)((0, path_1.basename)(inputName).replace(/\.[^.]+$/, '') + '.qnce.json'); (0, fs_1.writeFileSync)(outPath, JSON.stringify(story, null, 2)); console.log(`✅ Imported and normalized to ${outPath}`); } else { // stdout process.stdout.write(JSON.stringify(story, null, 2)); } await telemetry?.flush(); process.exit(exitCode); } catch (err) { console.error('❌ Import failed:', err?.message || err); try { await telemetry?.flush(); } catch { } process.exit(2); } } const isMainModule = require.main === module; if (isMainModule) { main().catch((e) => { console.error('❌ Import failed:', e?.message || e); process.exit(2); }); } //# sourceMappingURL=import.js.map