# reflection/nulib/interactive.nu — Interactive command picker and group routing. # Provides the "?" selector, missing-target handler, and run-group-command dispatch. use ../modules/describe.nu * use ../modules/config.nu * use ../modules/sync.nu * use ../modules/coder.nu * use ../modules/manifest.nu * use ../modules/backlog.nu * use ../modules/adr.nu * use ./help.nu [help-group] export def group-command-info [group: string]: nothing -> list { match $group { "describe" => [ { name: project, desc: "philosophy, axioms, state, gates", args: [] }, { name: capabilities, desc: "just modules, modes, commands, .claude", args: [] }, { name: constraints, desc: "invariants, Hard constraints, gates", args: [] }, { name: tools, desc: "dev tools, CI tools, just recipes", args: [] }, { name: features, desc: "project features list or detail", args: [{name: "id", prompt: "feature id (empty=list all)", optional: true}] }, { name: impact, desc: "if I change X, what's affected?", args: [{name: "node_id", prompt: "node id", optional: false}] }, { name: why, desc: "why does X exist?", args: [{name: "id", prompt: "node/adr id", optional: false}] }, ], "backlog" => [ { name: roadmap, desc: "state dimensions + open items by priority", args: [] }, { name: list, desc: "open backlog items", args: [] }, { name: show, desc: "show full backlog item", args: [{name: "id", prompt: "item id", optional: false}] }, { name: add, desc: "create new backlog item", args: [{name: "title", prompt: "title", optional: false}] }, { name: done, desc: "mark item as done", args: [{name: "id", prompt: "item id", optional: false}] }, { name: cancel, desc: "cancel item", args: [{name: "id", prompt: "item id", optional: false}] }, { name: promote, desc: "promote item priority", args: [{name: "id", prompt: "item id", optional: false}] }, ], "config" => [ { name: show, desc: "display config profile", args: [{name: "profile", prompt: "profile name", optional: false}] }, { name: history, desc: "config change history", args: [{name: "profile", prompt: "profile name", optional: false}] }, { name: diff, desc: "diff between profiles or versions", args: [{name: "profile", prompt: "profile name", optional: false}, {name: "from_id", prompt: "from id", optional: false}, {name: "to_id", prompt: "to id", optional: false}] }, { name: verify, desc: "verify profile integrity", args: [{name: "profile", prompt: "profile name", optional: false}] }, { name: audit, desc: "full config audit", args: [] }, { name: apply, desc: "apply config profile", args: [{name: "profile", prompt: "profile name", optional: false}] }, { name: rollback, desc: "rollback to previous config", args: [{name: "profile", prompt: "profile name", optional: false}, {name: "to_id", prompt: "target id", optional: false}] }, ], "sync" => [ { name: scan, desc: "analyze project artifacts", args: [] }, { name: diff, desc: "compare scan vs ontology", args: [] }, { name: propose, desc: "generate NCL patches for drift", args: [] }, { name: apply, desc: "apply proposed changes with confirmation", args: [] }, { name: state, desc: "sync state.ncl dimensions", args: [] }, { name: audit, desc: "full ontology↔code audit", args: [] }, { name: watch, desc: "continuous drift monitoring", args: [] }, ], "coder" => [ { name: authors, desc: "list registered authors", args: [] }, { name: init, desc: "initialize author workspace", args: [{name: "author", prompt: "author name", optional: false}] }, { name: record, desc: "record structured entry", args: [{name: "author", prompt: "author name", optional: false}, {name: "title", prompt: "title", optional: false}, {name: "content", prompt: "content", optional: false}] }, { name: log, desc: "query recorded entries", args: [] }, { name: export, desc: "export entries (json/jsonl/csv)", args: [] }, { name: triage, desc: "classify inbox markdown", args: [{name: "author", prompt: "author name", optional: false}] }, { name: ls, desc: "list files in author workspace", args: [{name: "author", prompt: "author name (empty=all)", optional: true}] }, { name: search, desc: "search across entries", args: [{name: "pattern", prompt: "search pattern", optional: false}] }, { name: publish, desc: "promote entries to general/", args: [{name: "author", prompt: "author name", optional: false}, {name: "category", prompt: "category", optional: false}] }, { name: graduate, desc: "copy to committed knowledge path", args: [{name: "source", prompt: "source category", optional: false}] }, ], "manifest" => [ { name: mode, desc: "activate operational mode", args: [{name: "id", prompt: "mode id", optional: false}] }, { name: publish, desc: "publish artifact to registry", args: [{name: "id", prompt: "service id", optional: false}] }, { name: layers, desc: "show manifest layers", args: [] }, { name: consumers, desc: "show consumption modes", args: [] }, ], "adr" => [ { name: list, desc: "list all ADRs with status", args: [] }, { name: validate, desc: "run Hard constraint checks", args: [] }, { name: accept, desc: "Proposed → Accepted", args: [{name: "id", prompt: "adr id (e.g. adr-001)", optional: false}] }, { name: show, desc: "show ADR detail", args: [{name: "id", prompt: "adr id (empty=interactive)", optional: true}] }, ], _ => [], } } export def group-subcommands [group: string]: nothing -> list { group-command-info $group | get name | each { |n| $n | into string } } export def run-interactive [group: string] { let cmds = (group-command-info $group) if ($cmds | is-empty) { help-group $group return } let labels = ($cmds | each { |c| let n = ($c.name | into string) let pad_width = ([(16 - ($n | str length)), 0] | math max) let pad = (" " | fill -w $pad_width -c ' ') $"($n)($pad)($c.desc)" }) let picked_label = ($labels | input list $"(ansi cyan_bold)($group):(ansi reset) ") if ($picked_label | is-empty) { return } let idx = ($labels | enumerate | where { |e| $e.item == $picked_label } | first | get index) let cmd_info = ($cmds | get $idx) let sub = ($cmd_info.name | into string) print $" (ansi dark_gray)($cmd_info.desc)(ansi reset)" mut collected_args: list = [] for arg in $cmd_info.args { let val = try { input $" (ansi cyan)($arg.prompt):(ansi reset) " } catch { |err| if ($err.msg | str contains "interrupted") { print $"\n (ansi yellow)cancelled(ansi reset)" return } error make { msg: $err.msg } } if ($val | is-empty) and (not $arg.optional) { print $" (ansi yellow)required(ansi reset)" return } $collected_args = ($collected_args | append $val) } print "" let result = (run-group-command $group $sub $collected_args) if ($result | describe) != "nothing" { print $result } } export def run-group-command [group: string, sub: string, args: list] { let a0 = ($args | get -o 0 | default "") let a1 = ($args | get -o 1 | default "") let a2 = ($args | get -o 2 | default "") match $group { "describe" => { match $sub { "project" => { describe project }, "capabilities" => { describe capabilities }, "constraints" => { describe constraints }, "tools" => { describe tools }, "features" => { if ($a0 | is-empty) { describe features } else { describe features $a0 } }, "impact" => { describe impact $a0 }, "why" => { describe why $a0 }, _ => {}, } }, "backlog" => { match $sub { "roadmap" => { backlog roadmap }, "list" => { backlog list }, "show" => { backlog show $a0 }, "add" => { backlog add $a0 }, "done" => { backlog done $a0 }, "cancel" => { backlog cancel $a0 }, "promote" => { backlog promote $a0 }, _ => {}, } }, "config" => { match $sub { "show" => { config show $a0 }, "history" => { config history $a0 }, "diff" => { config diff $a0 $a1 $a2 }, "verify" => { config verify $a0 }, "audit" => { config audit }, "apply" => { config apply $a0 }, "rollback" => { config rollback $a0 $a1 }, _ => {}, } }, "sync" => { match $sub { "scan" => { sync scan }, "diff" => { sync diff }, "propose" => { sync propose }, "apply" => { sync apply }, "state" => { sync state }, "audit" => { sync audit }, "watch" => { sync watch }, _ => {}, } }, "coder" => { match $sub { "authors" => { coder authors }, "init" => { coder init $a0 }, "record" => { coder record $a0 --title $a1 $a2 }, "log" => { coder log }, "export" => { coder export }, "triage" => { coder triage $a0 }, "ls" => { if ($a0 | is-empty) { coder ls } else { coder ls $a0 } }, "search" => { coder search $a0 }, "publish" => { coder publish $a0 $a1 }, "graduate" => { coder graduate $a0 }, _ => {}, } }, "manifest" => { match $sub { "mode" => { manifest mode $a0 }, "publish" => { manifest publish $a0 }, "layers" => { manifest layers }, "consumers" => { manifest consumers }, _ => {}, } }, "adr" => { match $sub { "list" => { adr list }, "validate" => { adr validate }, "accept" => { adr accept $a0 }, "show" => { if ($a0 | is-empty) { adr show --interactive } else { adr show $a0 } }, _ => {}, } }, _ => {}, } } export def missing-target [group: string, action?: string] { let act = ($action | default "") if $act == "h" or $act == "help" { help-group $group return } if $act == "?" or $act == "select" or $act == "" { run-interactive $group return } let cmd = ($env.ONTOREF_CALLER? | default "ontoref") print $" (ansi yellow)($group)(ansi reset): unknown subcommand '($act)'. Run '(ansi green)($cmd) ($group) h(ansi reset)' for options." }