coaian
Version:
Creative Orientation AI Agentic Memories - Narrative Beat Extension with IAIP relational integration
524 lines (504 loc) • 27.1 kB
JavaScript
/**
* COAIA Narrative CLI - Interactive Chart Visualizer
*
* Essential commands for human interaction with structural tension charts
* Provides visual, intuitive interface for chart management
*/
import { promises as fs } from 'fs';
import path from 'path';
import minimist from 'minimist';
// ==================== UTILITIES ====================
async function loadGraph(memoryPath) {
try {
const data = await fs.readFile(memoryPath, "utf-8");
const lines = data.split("\n").filter(line => line.trim() !== "");
return lines.reduce((graph, line) => {
const item = JSON.parse(line);
if (item.type === "entity")
graph.entities.push(item);
if (item.type === "relation")
graph.relations.push(item);
return graph;
}, { entities: [], relations: [] });
}
catch (error) {
if (error.code === "ENOENT") {
return { entities: [], relations: [] };
}
throw error;
}
}
function formatDate(dateStr) {
if (!dateStr)
return "No due date";
const date = new Date(dateStr);
const now = new Date();
const days = Math.ceil((date.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
if (days < 0)
return `⚠️ Overdue by ${Math.abs(days)} days`;
if (days === 0)
return "📅 Due today";
if (days === 1)
return "📅 Due tomorrow";
if (days <= 7)
return `📅 Due in ${days} days`;
return `📅 ${date.toLocaleDateString()}`;
}
function getProgressBar(progress, width = 20) {
const filled = Math.round(progress * width);
const empty = width - filled;
const bar = '█'.repeat(filled) + '░'.repeat(empty);
const percent = Math.round(progress * 100);
return `${bar} ${percent}%`;
}
function wordWrap(text, maxWidth) {
const words = text.split(' ');
const lines = [];
let currentLine = '';
for (const word of words) {
if ((currentLine + word).length <= maxWidth) {
currentLine += (currentLine ? ' ' : '') + word;
}
else {
if (currentLine)
lines.push(currentLine);
currentLine = word;
}
}
if (currentLine)
lines.push(currentLine);
return lines;
}
// ==================== COMMANDS ====================
async function listCharts(memoryPath) {
const graph = await loadGraph(memoryPath);
const charts = graph.entities.filter(e => e.entityType === 'structural_tension_chart');
if (charts.length === 0) {
console.log('\n📊 No structural tension charts found.\n');
console.log('💡 Create your first chart with: cnarrative create\n');
return;
}
console.log('\n╔═══════════════════════════════════════════════════════════════════════════════╗');
console.log('║ 📊 STRUCTURAL TENSION CHARTS - ACTIVE HIERARCHY ║');
console.log('╚═══════════════════════════════════════════════════════════════════════════════╝\n');
const masterCharts = charts.filter(c => c.metadata?.level === 0);
for (const master of masterCharts) {
const chartId = master.metadata?.chartId || master.name.replace('_chart', '');
const outcome = graph.entities.find(e => e.name === `${chartId}_desired_outcome` && e.entityType === 'desired_outcome');
const currentReality = graph.entities.find(e => e.name === `${chartId}_current_reality` && e.entityType === 'current_reality');
const actionSteps = graph.entities.filter(e => e.entityType === 'action_step' && e.metadata?.chartId === chartId);
const completed = actionSteps.filter(a => a.metadata?.completionStatus === true).length;
const total = actionSteps.length;
const progress = total > 0 ? completed / total : 0;
// Master chart header
console.log(`┌─────────────────────────────────────────────────────────────────────────────┐`);
console.log(`│ 🎯 MASTER CHART: ${chartId.padEnd(60)} │`);
console.log(`├─────────────────────────────────────────────────────────────────────────────┤`);
// Desired Outcome
const outcomeText = outcome?.observations[0] || 'Unknown';
const outcomeLines = wordWrap(outcomeText, 73);
console.log(`│ 🌟 DESIRED OUTCOME: │`);
outcomeLines.forEach(line => {
console.log(`│ ${line.padEnd(73)} │`);
});
// Progress
console.log(`│ │`);
console.log(`│ ${getProgressBar(progress, 40).padEnd(73)} │`);
console.log(`│ Completed: ${completed}/${total} action steps`.padEnd(76) + '│');
// Due date
console.log(`│ ${formatDate(master.metadata?.dueDate).padEnd(73)} │`);
// Current Reality
console.log(`│ │`);
console.log(`│ 🔍 CURRENT REALITY: │`);
const realityText = currentReality?.observations.slice(-3).join('; ') || 'Not assessed';
const realityLines = wordWrap(realityText, 73);
realityLines.forEach(line => {
console.log(`│ ${line.padEnd(73)} │`);
});
console.log(`└─────────────────────────────────────────────────────────────────────────────┘`);
// Action steps (telescoped charts)
const actionCharts = charts.filter(c => c.metadata?.parentChart === chartId && c.metadata?.level === 1);
if (actionCharts.length > 0) {
console.log(`\n 📋 ACTION STEPS:\n`);
actionCharts.forEach((actionChart, idx) => {
const actionChartId = actionChart.metadata?.chartId;
const actionOutcome = graph.entities.find(e => e.name === `${actionChartId}_desired_outcome` && e.entityType === 'desired_outcome');
const actionActions = graph.entities.filter(e => e.entityType === 'action_step' && e.metadata?.chartId === actionChartId);
const actionCompleted = actionActions.filter(a => a.metadata?.completionStatus === true).length;
const actionTotal = actionActions.length;
const actionProgress = actionTotal > 0 ? actionCompleted / actionTotal : 0;
const isComplete = actionChart.metadata?.completionStatus === true;
const isLast = idx === actionCharts.length - 1;
const prefix = isLast ? ' └──' : ' ├──';
const status = isComplete ? '✅' : (actionProgress > 0 ? '🔄' : '⏳');
console.log(`${prefix} ${status} ${actionOutcome?.observations[0] || 'Unknown'}`);
console.log(` ${isLast ? ' ' : '│ '} ID: ${actionChartId} | ${formatDate(actionChart.metadata?.dueDate)}`);
if (actionTotal > 0) {
console.log(` ${isLast ? ' ' : '│ '} ${getProgressBar(actionProgress, 30)}`);
}
console.log('');
});
}
else {
console.log(`\n 📋 No action steps yet.\n`);
}
console.log('');
}
console.log('═'.repeat(79));
console.log(`💡 Use 'cnarrative view <chartId>' to see detailed chart information`);
console.log(`💡 Use 'cnarrative help' to see all available commands\n`);
}
async function viewChart(chartId, memoryPath) {
const graph = await loadGraph(memoryPath);
const chart = graph.entities.find(e => e.entityType === 'structural_tension_chart' && e.metadata?.chartId === chartId);
if (!chart) {
console.log(`\n❌ Chart '${chartId}' not found.\n`);
console.log(`💡 Use 'cnarrative list' to see available charts.\n`);
return;
}
const outcome = graph.entities.find(e => e.name === `${chartId}_desired_outcome` && e.entityType === 'desired_outcome');
const currentReality = graph.entities.find(e => e.name === `${chartId}_current_reality` && e.entityType === 'current_reality');
const actionSteps = graph.entities.filter(e => e.entityType === 'action_step' && e.metadata?.chartId === chartId);
const narrativeBeats = graph.entities.filter(e => e.entityType === 'narrative_beat' && e.metadata?.chartId === chartId).sort((a, b) => (a.metadata?.act || 0) - (b.metadata?.act || 0));
console.log('\n╔═══════════════════════════════════════════════════════════════════════════════╗');
console.log(`║ STRUCTURAL TENSION CHART VIEW ║`);
console.log('╚═══════════════════════════════════════════════════════════════════════════════╝\n');
console.log(`📊 Chart ID: ${chartId}`);
console.log(`📅 Created: ${new Date(chart.metadata?.createdAt || '').toLocaleString()}`);
console.log(`📅 ${formatDate(chart.metadata?.dueDate)}`);
if (chart.metadata?.parentChart) {
console.log(`🔗 Parent Chart: ${chart.metadata.parentChart} (Level ${chart.metadata.level})`);
}
else {
console.log(`🎯 Master Chart (Level ${chart.metadata?.level || 0})`);
}
console.log('\n' + '─'.repeat(79));
console.log('\n🌟 DESIRED OUTCOME (What you want to CREATE):');
console.log('─'.repeat(79));
const outcomeText = outcome?.observations[0] || 'Unknown';
wordWrap(outcomeText, 75).forEach(line => console.log(` ${line}`));
console.log('\n' + '─'.repeat(79));
console.log('\n🔍 CURRENT REALITY (Where you are NOW):');
console.log('─'.repeat(79));
if (currentReality && currentReality.observations.length > 0) {
currentReality.observations.forEach((obs, idx) => {
console.log(` ${idx + 1}. ${obs}`);
});
}
else {
console.log(' (Not assessed)');
}
console.log('\n' + '─'.repeat(79));
console.log('\n⚡ STRUCTURAL TENSION:');
console.log('─'.repeat(79));
const completed = actionSteps.filter(a => a.metadata?.completionStatus === true).length;
const total = actionSteps.length;
const progress = total > 0 ? completed / total : 0;
console.log(` The gap between current reality and desired outcome creates natural`);
console.log(` momentum toward resolution. Progress advances the system toward equilibrium.`);
console.log(`\n ${getProgressBar(progress, 50)}`);
console.log(` ${completed} of ${total} action steps completed\n`);
if (actionSteps.length > 0) {
console.log('─'.repeat(79));
console.log('\n📋 ACTION STEPS (Strategic intermediary results):');
console.log('─'.repeat(79) + '\n');
actionSteps.forEach((step, idx) => {
const isComplete = step.metadata?.completionStatus === true;
const status = isComplete ? '✅' : '⏳';
const stepDue = formatDate(step.metadata?.dueDate);
console.log(` ${idx + 1}. ${status} ${step.observations[0]}`);
console.log(` Entity: ${step.name}`);
console.log(` ${stepDue}`);
if (step.observations.length > 1) {
console.log(` Progress notes:`);
step.observations.slice(1).forEach(note => {
console.log(` • ${note}`);
});
}
console.log('');
});
}
// Display narrative beats
if (narrativeBeats.length > 0) {
console.log('─'.repeat(79));
console.log('\n📖 NARRATIVE BEATS (Story progression):');
console.log('─'.repeat(79) + '\n');
narrativeBeats.forEach((beat, idx) => {
const act = beat.metadata?.act || '?';
const type = beat.metadata?.type_dramatic || 'Unknown';
const universes = beat.metadata?.universes || [];
const timestamp = beat.metadata?.timestamp
? new Date(beat.metadata.timestamp).toLocaleString()
: 'Unknown';
console.log(` ${idx + 1}. Act ${act}: ${type}`);
console.log(` 🌍 Universes: ${universes.join(', ')}`);
console.log(` 🕒 Timestamp: ${timestamp}`);
if (beat.metadata?.narrative?.description) {
console.log(`\n 📝 Description:`);
wordWrap(beat.metadata.narrative.description, 72).forEach(line => {
console.log(` ${line}`);
});
}
if (beat.metadata?.narrative?.prose) {
console.log(`\n ✨ Prose:`);
wordWrap(beat.metadata.narrative.prose, 72).forEach(line => {
console.log(` ${line}`);
});
}
if (beat.metadata?.narrative?.lessons && beat.metadata.narrative.lessons.length > 0) {
console.log(`\n 💡 Lessons:`);
beat.metadata.narrative.lessons.forEach(lesson => {
wordWrap(lesson, 68).forEach((line, i) => {
console.log(` ${i === 0 ? '•' : ' '} ${line}`);
});
});
}
// Four Directions if present
const dirs = beat.metadata?.fourDirections;
if (dirs && (dirs.north_vision || dirs.east_intention || dirs.south_emotion || dirs.west_introspection)) {
console.log(`\n 🧭 Four Directions:`);
if (dirs.north_vision)
console.log(` North (Vision): ${dirs.north_vision}`);
if (dirs.east_intention)
console.log(` East (Intention): ${dirs.east_intention}`);
if (dirs.south_emotion)
console.log(` South (Emotion): ${dirs.south_emotion}`);
if (dirs.west_introspection)
console.log(` West (Introspection): ${dirs.west_introspection}`);
}
// Relational alignment if assessed
const align = beat.metadata?.relationalAlignment;
if (align?.assessed && align.score !== null) {
console.log(`\n 🤝 Relational Alignment: ${align.score}/10`);
if (align.principles && align.principles.length > 0) {
console.log(` Principles: ${align.principles.join(', ')}`);
}
}
console.log('');
});
}
// Check for telescoped sub-charts
const subCharts = graph.entities.filter(e => e.entityType === 'structural_tension_chart' &&
e.metadata?.parentChart === chartId);
if (subCharts.length > 0) {
console.log('─'.repeat(79));
console.log('\n🔭 TELESCOPED SUB-CHARTS:');
console.log('─'.repeat(79) + '\n');
subCharts.forEach(sub => {
const subId = sub.metadata?.chartId;
const subOutcome = graph.entities.find(e => e.name === `${subId}_desired_outcome`);
console.log(` • ${subOutcome?.observations[0] || 'Unknown'}`);
console.log(` Chart ID: ${subId}`);
console.log('');
});
}
console.log('═'.repeat(79));
console.log(`💡 Use 'cnarrative update ${chartId}' to modify this chart`);
console.log(`💡 Use 'cnarrative list' to see all charts\n`);
}
function showHelp() {
console.log(`
╔═══════════════════════════════════════════════════════════════════════════════╗
║ COAIA NARRATIVE CLI - Structural Tension Chart Visualizer ║
╚═══════════════════════════════════════════════════════════════════════════════╝
USAGE:
cnarrative <command> [options]
COMMANDS:
📊 VIEWING COMMANDS
───────────────────────────────────────────────────────────────────────────────
list List all structural tension charts in hierarchy
view <chartId> View detailed information for a specific chart
📈 QUICK STATS
───────────────────────────────────────────────────────────────────────────────
stats Show summary statistics across all charts
progress <chartId> Show progress details for a specific chart
⚙️ UTILITY
───────────────────────────────────────────────────────────────────────────────
help Show this help message
version Show version information
OPTIONS:
--memory-path <path> Path to memory JSONL file (default: ./memory.jsonl)
-M <path> Short alias for --memory-path
--no-color Disable colored output
--json Output in JSON format
EXAMPLES:
# List all charts with visual hierarchy
cnarrative list
# View detailed information for a specific chart
cnarrative view chart_1234567890
# View chart using custom memory path
cnarrative view chart_123 --memory-path /path/to/memory.jsonl
# Same using short alias
cnarrative view chart_123 -M /path/to/memory.jsonl
# Get statistics in JSON format
cnarrative stats --json
PHILOSOPHY:
Structural Tension Charts organize creative processes around desired outcomes
rather than problem-solving. The unresolved tension between current reality
and desired outcome naturally seeks resolution through advancing patterns.
🌟 Desired Outcome = What you want to CREATE
🔍 Current Reality = Honest assessment of where you are NOW
⚡ Structural Tension = The gap that creates natural momentum
📋 Action Steps = Strategic intermediary results
MORE INFO:
MCP Server: Use 'coaia-narrative' (MCP protocol) for AI assistant integration
Documentation: See README.md for complete methodology
Author: Based on Robert Fritz's Structural Tension principles
`);
}
async function showStats(memoryPath, jsonOutput = false) {
const graph = await loadGraph(memoryPath);
const charts = graph.entities.filter(e => e.entityType === 'structural_tension_chart');
const masterCharts = charts.filter(c => c.metadata?.level === 0);
const actionCharts = charts.filter(c => c.metadata?.level === 1);
const narrativeBeats = graph.entities.filter(e => e.entityType === 'narrative_beat');
let totalActions = 0;
let completedActions = 0;
let overdueCharts = 0;
const now = new Date();
charts.forEach(chart => {
const chartId = chart.metadata?.chartId;
if (!chartId)
return;
const actions = graph.entities.filter(e => e.entityType === 'action_step' && e.metadata?.chartId === chartId);
totalActions += actions.length;
completedActions += actions.filter(a => a.metadata?.completionStatus === true).length;
if (chart.metadata?.dueDate) {
const dueDate = new Date(chart.metadata.dueDate);
if (dueDate < now && chart.metadata?.completionStatus !== true) {
overdueCharts++;
}
}
});
const stats = {
totalCharts: charts.length,
masterCharts: masterCharts.length,
actionCharts: actionCharts.length,
narrativeBeats: narrativeBeats.length,
totalActions,
completedActions,
overdueCharts,
overallProgress: totalActions > 0 ? completedActions / totalActions : 0
};
if (jsonOutput) {
console.log(JSON.stringify(stats, null, 2));
return;
}
console.log('\n╔═══════════════════════════════════════════════════════════════════════════════╗');
console.log('║ 📊 STRUCTURAL TENSION CHARTS STATISTICS ║');
console.log('╚═══════════════════════════════════════════════════════════════════════════════╝\n');
console.log(` 📋 Total Charts: ${stats.totalCharts}`);
console.log(` • Master Charts: ${stats.masterCharts}`);
console.log(` • Action Step Charts: ${stats.actionCharts}`);
console.log('');
console.log(` ✅ Action Steps: ${stats.completedActions} / ${stats.totalActions} completed`);
console.log(` ${getProgressBar(stats.overallProgress, 50)}`);
console.log('');
if (stats.narrativeBeats > 0) {
console.log(` 📖 Narrative Beats: ${stats.narrativeBeats}`);
console.log('');
}
if (stats.overdueCharts > 0) {
console.log(` ⚠️ Overdue Charts: ${stats.overdueCharts}`);
}
else {
console.log(` ✨ All charts on track!`);
}
console.log('\n' + '═'.repeat(79) + '\n');
}
async function showProgress(chartId, memoryPath) {
const graph = await loadGraph(memoryPath);
const chart = graph.entities.find(e => e.entityType === 'structural_tension_chart' && e.metadata?.chartId === chartId);
if (!chart) {
console.log(`\n❌ Chart '${chartId}' not found.\n`);
return;
}
const outcome = graph.entities.find(e => e.name === `${chartId}_desired_outcome`);
const actions = graph.entities.filter(e => e.entityType === 'action_step' && e.metadata?.chartId === chartId);
const completed = actions.filter(a => a.metadata?.completionStatus === true);
const incomplete = actions.filter(a => a.metadata?.completionStatus !== true);
const progress = actions.length > 0 ? completed.length / actions.length : 0;
console.log('\n╔═══════════════════════════════════════════════════════════════════════════════╗');
console.log('║ CHART PROGRESS REPORT ║');
console.log('╚═══════════════════════════════════════════════════════════════════════════════╝\n');
console.log(`📊 Chart: ${chartId}`);
console.log(`🌟 Goal: ${outcome?.observations[0] || 'Unknown'}\n`);
console.log(`${getProgressBar(progress, 60)}\n`);
console.log(`✅ Completed: ${completed.length}`);
if (completed.length > 0) {
completed.forEach(action => {
console.log(` • ${action.observations[0]}`);
});
}
console.log('');
console.log(`⏳ Remaining: ${incomplete.length}`);
if (incomplete.length > 0) {
incomplete.forEach(action => {
const due = formatDate(action.metadata?.dueDate);
console.log(` • ${action.observations[0]} (${due})`);
});
}
console.log('\n' + '═'.repeat(79) + '\n');
}
// ==================== MAIN ====================
async function main() {
const args = minimist(process.argv.slice(2));
const command = args._[0];
const memoryPath = args['memory-path'] || args['M'] || path.join(process.cwd(), 'memory.jsonl');
const jsonOutput = args.json === true;
try {
switch (command) {
case 'list':
case 'ls':
await listCharts(memoryPath);
break;
case 'view':
case 'show':
if (!args._[1]) {
console.log('\n❌ Error: Chart ID required\n');
console.log('Usage: cnarrative view <chartId>\n');
process.exit(1);
}
await viewChart(args._[1], memoryPath);
break;
case 'stats':
case 'statistics':
await showStats(memoryPath, jsonOutput);
break;
case 'progress':
if (!args._[1]) {
console.log('\n❌ Error: Chart ID required\n');
console.log('Usage: cnarrative progress <chartId>\n');
process.exit(1);
}
await showProgress(args._[1], memoryPath);
break;
case 'help':
case '--help':
case '-h':
showHelp();
break;
case 'version':
case '--version':
case '-v':
console.log('\nCOAIA Narrative CLI v0.5.0');
console.log('Structural Tension Chart Visualizer\n');
break;
default:
if (!command) {
showHelp();
}
else {
console.log(`\n❌ Unknown command: ${command}\n`);
console.log(`💡 Use 'cnarrative help' to see available commands\n`);
process.exit(1);
}
}
}
catch (error) {
console.error('\n❌ Error:', error.message, '\n');
process.exit(1);
}
}
main();