ontoref/install/install.nu
Jesús Pérez 472952e29b
Some checks failed
Rust CI / Security Audit (push) Has been cancelled
Rust CI / Check + Test + Lint (nightly) (push) Has been cancelled
Rust CI / Check + Test + Lint (stable) (push) Has been cancelled
Nickel Type Check / Nickel Type Checking (push) Has been cancelled
feat: domain extension system, VCS abstraction, personal/provisioning domains, web subpages
Domain extension system (ADR-012): bash-layer dispatch activates repo_kind-conditional CLI
  domains. install.nu copies domains/ tree; short_alias wrappers generated (personal, prov).
  ore help and describe capabilities domain-aware.

  personal domain (PersonalOntology): career skills/talks/publications/positioning, CFP
  pipeline (Watching→Delivered), opportunities lifecycle, content pipeline, Sessionize
  integration. Daemon pages: /career, /personal.

  provisioning domain (DevWorkspace/Mixed): FSM state, next transitions, connections graph,
  gates, workspace card, capabilities, backlog. Daemon page: /provisioning.

  VCS abstraction layer (ADR-013): reflection/modules/vcs.nu — uniform jj/git API via
  filesystem detection (.jj/ vs .git/). opmode.nu and git-event.nu migrated off ^git.
  reflection/bin/jjw.nu — jj + ontoref + Radicle agent workspace lifecycle. jjw-ncl-merge.nu
  registered as jj merge tool for .ontology/ NCL conflicts. init-repo.nu for new_project mode.
  jj/rad not in ontoref requirements — belong in orchestration project manifests.

  'Framework RepoKind: ontology/schemas/manifest.ncl gains 'Framework variant; ontoref
  self-identifies as framework — no domain activates for the protocol itself.

  Web presence: personal.html and provisioning.html domain subpages. index.html gains
  "Project Types — Domain Extensions" section with type cards and subpage links. Nav
  compacted (Arch/Prov labels, solid backdrop-filter background).

  on+re: vcs-abstraction (adrs: adr-013) and agent-workspace-orchestration Practice nodes;
  21 manifest capabilities; state.ncl catalysts updated.
2026-04-07 23:08:29 +01:00

417 lines
17 KiB
Text
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env nu
# install/install.nu — ontoref-daemon installer
#
# Installed layout:
#
# ~/.local/bin/
# ontoref ← global CLI wrapper (install/ontoref-global)
# ontoref-daemon ← bootstrapper: nickel export | ontoref-daemon.bin (ADR-004)
# ontoref-daemon.bin ← compiled binary (not called directly)
#
# Platform data/config:
# macOS:
# data ~/Library/Application Support/ontoref/ templates, public, nulib
# config ~/.config/ontoref/
# Linux:
# data ~/.local/share/ontoref/
# config ~/.config/ontoref/
#
# Dev mode (Cargo.toml present + nu in PATH): also installs ncl-bootstrap Nu helper
# Service mode: bootstrapper is the sole entrypoint — .bin never called directly
def install-if-changed [src: string, dest: string, label: string] {
let needs_update = if ($dest | path exists) {
(open --raw $src | hash sha256) != (open --raw $dest | hash sha256)
} else {
true
}
if $needs_update {
cp $src $dest
chmod +x $dest
print $"✓ ($label) ($dest)"
} else {
print $"— ($label) unchanged"
}
}
def main [] {
let repo_root = $env.PWD
let is_dev = (("Cargo.toml" | path exists) and ((which nu | length) > 0))
let platform = (sys host | get name)
let is_mac = ((^uname) == "Darwin")
let data_dir = if $is_mac {
$"($env.HOME)/Library/Application Support/ontoref"
} else {
$"($env.HOME)/.local/share/ontoref"
}
let config_dir = $"($env.HOME)/.config/ontoref"
let bin_dir = $"($env.HOME)/.local/bin"
mkdir $bin_dir
# ── 1. Binary → ontoref-daemon.bin ────────────────────────────────────────
let bin_src = $"($repo_root)/target/release/ontoref-daemon"
let bin_dest = $"($bin_dir)/ontoref-daemon.bin"
if not ($bin_src | path exists) {
error make { msg: $"binary not found: ($bin_src)\n run: cargo build --release -p ontoref-daemon" }
}
# Stop any running instance — macOS kills the new process if the old one holds the file
do { ^pkill -x ontoref-daemon.bin } | ignore
do { ^pkill -x ontoref-daemon } | ignore
cp $bin_src $bin_dest
chmod +x $bin_dest
#if $is_mac {
# do { ^xattr -d com.apple.quarantine $bin_dest } | ignore
#}
print $"✓ binary ($bin_dest)"
# ── 2. Bootstrapper → ontoref-daemon ──────────────────────────────────────
# The bootstrapper IS the public entrypoint. Users call ontoref-daemon, never .bin directly.
let boot_src = $"($repo_root)/install/ontoref-daemon-boot"
let boot_dest = $"($bin_dir)/ontoref-daemon"
install-if-changed $boot_src $boot_dest "bootstrapper"
# ── 3. Global CLI wrapper → ontoref ───────────────────────────────────────
# Bake the data dir as ONTOREF_ROOT so the installed wrapper is self-contained
# and does not require the source repo to be present at runtime.
let cli_src = $"($repo_root)/install/ontoref-global"
let cli_dest = $"($bin_dir)/ontoref"
let cli_baked = (
open --raw $cli_src
| str replace 'ONTOREF_ROOT="${ONTOREF_ROOT:-ontoref}"' $'ONTOREF_ROOT="${ONTOREF_ROOT:-($data_dir)}"'
)
let needs_update = if ($cli_dest | path exists) {
($cli_baked | hash sha256) != (open --raw $cli_dest | hash sha256)
} else {
true
}
if $needs_update {
$cli_baked | save --force $cli_dest
chmod +x $cli_dest
print $"✓ cli ($cli_dest)"
} else {
print $"— cli unchanged"
}
# ── 3b. Reflection scripts (data dir) ─────────────────────────────────────
# The global CLI wrapper calls $data_dir/reflection/bin/ontoref.nu directly.
# Copy the entire reflection/ tree so the install is autonomous (no dev repo needed).
let reflection_src = $"($repo_root)/reflection"
let reflection_dest = $"($data_dir)/reflection"
if not ($reflection_src | path exists) {
error make { msg: $"reflection/ not found: ($reflection_src)" }
}
mkdir $reflection_dest
mut refl_updated = 0
mut refl_skipped = 0
for src_file in (glob $"($reflection_src)/**/*" | where { |f| ($f | path type) == "file" }) {
let rel = ($src_file | str replace $"($reflection_src)/" "")
let dest_file = $"($reflection_dest)/($rel)"
let dest_parent = ($dest_file | path dirname)
mkdir $dest_parent
let needs_update = if ($dest_file | path exists) {
(open --raw $src_file | hash sha256) != (open --raw $dest_file | hash sha256)
} else {
true
}
if $needs_update {
cp $src_file $dest_file
$refl_updated = $refl_updated + 1
} else {
$refl_skipped = $refl_skipped + 1
}
}
print $"✓ reflection ($reflection_dest)/ updated=($refl_updated) unchanged=($refl_skipped)"
# ── 3c. Domain extensions (domains/) ─────────────────────────────────────
# Domain commands are dispatched by the bash wrapper at runtime from $ONTOREF_ROOT/domains/.
let domains_src = $"($repo_root)/domains"
let domains_dest = $"($data_dir)/domains"
if ($domains_src | path exists) {
mkdir $domains_dest
mut dom_updated = 0
mut dom_skipped = 0
for src_file in (glob $"($domains_src)/**/*" | where { |f| ($f | path type) == "file" }) {
let rel = ($src_file | str replace $"($domains_src)/" "")
let dest_file = $"($domains_dest)/($rel)"
let dest_parent = ($dest_file | path dirname)
mkdir $dest_parent
let needs_update = if ($dest_file | path exists) {
(open --raw $src_file | hash sha256) != (open --raw $dest_file | hash sha256)
} else {
true
}
if $needs_update {
cp $src_file $dest_file
$dom_updated = $dom_updated + 1
} else {
$dom_skipped = $dom_skipped + 1
}
}
print $"✓ domains ($domains_dest)/ updated=($dom_updated) unchanged=($dom_skipped)"
# Build aliases.txt and install short_alias bin wrappers for each domain.
mut alias_lines = []
for domain_dir in (ls $domains_src | where type == "dir" | get name) {
let domain_ncl = $"($domain_dir)/domain.ncl"
if not ($domain_ncl | path exists) { continue }
let domain_data = (do { ^nickel export $domain_ncl } | complete)
if $domain_data.exit_code != 0 { continue }
let d = ($domain_data.stdout | from json)
let alias = ($d.short_alias? | default "")
let domain_id = ($d.id? | default "")
if ($alias | is-empty) { continue }
# aliases.txt entry — only when alias differs from domain id (e.g. prov → provisioning)
# skipped when alias == domain_id (e.g. personal → personal): ore already handles it natively
if $alias != $domain_id {
$alias_lines = ($alias_lines | append $"($alias)=($domain_id)")
}
# standalone bin wrapper — always created when short_alias is set,
# even when alias == domain_id, so `personal state` works without `ore`
let alias_dest = $"($bin_dir)/($alias)"
let alias_body = $"#!/bin/bash\nexec ontoref ($domain_id) \"$@\"\n"
let needs_update = if ($alias_dest | path exists) {
($alias_body | hash sha256) != (open --raw $alias_dest | hash sha256)
} else { true }
if $needs_update {
$alias_body | save --force $alias_dest
chmod +x $alias_dest
print $"✓ alias ($alias_dest) → ontoref ($domain_id)"
} else {
print $"— alias ($alias) unchanged"
}
}
# Write consolidated aliases.txt to the installed domains dir
let aliases_dest = $"($domains_dest)/aliases.txt"
if ($alias_lines | is-not-empty) {
let aliases_body = ($alias_lines | str join "\n")
let needs_update = if ($aliases_dest | path exists) {
($aliases_body | hash sha256) != (open --raw $aliases_dest | hash sha256)
} else { true }
if $needs_update {
$aliases_body | save --force $aliases_dest
print $"✓ domain-aliases ($aliases_dest)"
} else {
print $"— domain-aliases unchanged"
}
}
} else {
print $" (ansi yellow)warn(ansi reset) domains/ not found at ($domains_src)"
}
# ── 3d. CLI templates (project.ncl, ontoref-config.ncl, ontology/ stubs) ──
# `ontoref setup` reads from $ONTOREF_ROOT/templates/ — copy the repo-level
# templates/ tree so the installed CLI works without the source repo present.
let cli_templates_src = $"($repo_root)/templates"
let cli_templates_dest = $"($data_dir)/templates"
if ($cli_templates_src | path exists) {
mkdir $cli_templates_dest
mut tmpl_updated = 0
mut tmpl_skipped = 0
for src_file in (glob $"($cli_templates_src)/**/*" | where { |f| ($f | path type) == "file" }) {
let rel = ($src_file | str replace $"($cli_templates_src)/" "")
let dest_file = $"($cli_templates_dest)/($rel)"
let dest_parent = ($dest_file | path dirname)
mkdir $dest_parent
let needs_update = if ($dest_file | path exists) {
(open --raw $src_file | hash sha256) != (open --raw $dest_file | hash sha256)
} else {
true
}
if $needs_update {
cp $src_file $dest_file
$tmpl_updated = $tmpl_updated + 1
} else {
$tmpl_skipped = $tmpl_skipped + 1
}
}
print $"✓ cli-templates ($cli_templates_dest)/ updated=($tmpl_updated) unchanged=($tmpl_skipped)"
} else {
print $" (ansi yellow)warn(ansi reset) templates/ not found at ($cli_templates_src)"
}
# ── 3e. Ontology defaults + schemas (data dir) ───────────────────────────
# Consumer projects import "ontology/defaults/state.ncl", "defaults/manifest.ncl", etc.
# These must be resolvable from $ONTOREF_ROOT (the data dir).
# Structure: $data_dir/ontology/{defaults,schemas}/
# The bash wrapper includes $ONTOREF_ROOT in NICKEL_IMPORT_PATH, so:
# import "ontology/defaults/state.ncl" → $data_dir/ontology/defaults/state.ncl ✓
# import "defaults/state.ncl" → $data_dir/ontology/defaults/state.ncl ✓ (via $ONTOREF_ROOT/ontology in nickel-import-path)
let ontology_src = $"($repo_root)/ontology"
let ontology_dest = $"($data_dir)/ontology"
if ($ontology_src | path exists) {
mkdir $ontology_dest
mut ont_updated = 0
mut ont_skipped = 0
for src_file in (glob $"($ontology_src)/**/*.ncl" | where { |f| ($f | path type) == "file" }) {
let rel = ($src_file | str replace $"($ontology_src)/" "")
let dest_file = $"($ontology_dest)/($rel)"
let dest_parent = ($dest_file | path dirname)
mkdir $dest_parent
let needs_update = if ($dest_file | path exists) {
(open --raw $src_file | hash sha256) != (open --raw $dest_file | hash sha256)
} else {
true
}
if $needs_update { cp $src_file $dest_file; $ont_updated = $ont_updated + 1 } else { $ont_skipped = $ont_skipped + 1 }
}
print $"✓ ontology ($ontology_dest)/ updated=($ont_updated) unchanged=($ont_skipped)"
}
# ── 4. UI assets (data dir) ────────────────────────────────────────────────
let templates_src = $"($repo_root)/crates/ontoref-daemon/templates"
let public_src = $"($repo_root)/crates/ontoref-daemon/public"
if not ($templates_src | path exists) {
error make { msg: $"templates not found: ($templates_src)" }
}
mkdir $data_dir
let asset_dirs = [$templates_src $public_src]
mut updated = 0
mut skipped = 0
for asset_dir in $asset_dirs {
let dir_name = ($asset_dir | path basename)
let dest_base = $"($data_dir)/($dir_name)"
mkdir $dest_base
for src_file in (glob $"($asset_dir)/**/*" | where { |f| ($f | path type) == "file" }) {
let rel = ($src_file | str replace $"($asset_dir)/" "")
let dest_file = $"($dest_base)/($rel)"
let dest_parent = ($dest_file | path dirname)
mkdir $dest_parent
let needs_update = if ($dest_file | path exists) {
(open --raw $src_file | hash sha256) != (open --raw $dest_file | hash sha256)
} else {
true
}
if $needs_update {
cp $src_file $dest_file
$updated = $updated + 1
} else {
$skipped = $skipped + 1
}
}
}
print $"✓ assets ($data_dir)/ updated=($updated) unchanged=($skipped)"
# ── 5. Config skeleton + global NATS topology ─────────────────────────────
let streams_default = $"($repo_root)/install/resources/streams.json"
let streams_dest = $"($config_dir)/streams.json"
if ($streams_default | path exists) {
mkdir $config_dir
if not ($streams_dest | path exists) {
cp $streams_default $streams_dest
print $"✓ nats topology ($streams_dest)"
} else {
print $"— nats topology unchanged"
}
}
let config_default = $"($repo_root)/install/resources/config.ncl"
let config_dest = $"($config_dir)/config.ncl"
let config_example = $"($config_dir)/config.ncl.example"
if ($config_default | path exists) {
mkdir $config_dir
cp $config_default $config_example
if ($config_dest | path exists) {
print $" config already exists — not overwritten"
print $" example kept: ($config_example)"
} else {
cp $config_default $config_dest
print $"✓ config ($config_dest)"
print $" edit with: ontoref config-edit"
}
}
# projects.ncl — local projects; populated by `ontoref project-add`
let projects_default = $"($repo_root)/install/resources/projects.ncl"
let projects_dest = $"($config_dir)/projects.ncl"
if ($projects_default | path exists) and not ($projects_dest | path exists) {
cp $projects_default $projects_dest
print $"✓ projects ($projects_dest)"
}
# remote-projects.ncl — remote/push-only projects; populated by `ontoref project-add-remote`
let remote_default = $"($repo_root)/install/resources/remote-projects.ncl"
let remote_dest = $"($config_dir)/remote-projects.ncl"
if ($remote_default | path exists) and not ($remote_dest | path exists) {
cp $remote_default $remote_dest
print $"✓ remote-projects ($remote_dest)"
}
# schemas/ — project contract schemas imported by per-project .ontoref/project.ncl
let schemas_src = $"($repo_root)/install/resources/schemas"
let schemas_dest = $"($config_dir)/schemas"
if ($schemas_src | path exists) {
mkdir $schemas_dest
for f in (ls $schemas_src | get name) {
let dest_f = $"($schemas_dest)/(($f | path basename))"
if not ($dest_f | path exists) {
cp $f $dest_f
print $"✓ schema ($dest_f)"
}
}
}
# ── 6. Install scripts (gen-projects.nu, etc.) + hooks ────────────────────
# The bootstrapper looks for *.nu at $data_dir/install/.
# `ontoref hooks-install` looks for install/hooks/{post-commit,post-merge}.
let install_dest = $"($data_dir)/install"
mkdir $install_dest
for f in (glob $"($repo_root)/install/*.nu") {
let dest_f = $"($install_dest)/(($f | path basename))"
install-if-changed $f $dest_f $"install/(($f | path basename))"
}
let hooks_src = $"($repo_root)/install/hooks"
let hooks_dest = $"($install_dest)/hooks"
if ($hooks_src | path exists) {
mkdir $hooks_dest
for f in (glob $"($hooks_src)/*" | where { |p| ($p | path type) == "file" }) {
let dest_f = $"($hooks_dest)/(($f | path basename))"
install-if-changed $f $dest_f $"install/hooks/(($f | path basename))"
}
}
# ── 7. Dev extras: ncl-bootstrap Nu helper ────────────────────────────────
if $is_dev {
let nulib_dest = $"($data_dir)/nulib"
mkdir $nulib_dest
cp $"($repo_root)/reflection/nulib/bootstrap.nu" $"($nulib_dest)/bootstrap.nu"
print $"✓ ncl-bootstrap ($nulib_dest)/bootstrap.nu"
}
# ── Summary ────────────────────────────────────────────────────────────────
let mode_tag = if $is_dev { "dev" } else { "service" }
print $"\ninstalled mode=($mode_tag) platform=($platform)"
print $" bin ($bin_dir)/ontoref, ontoref-daemon, ontoref-daemon.bin"
print $" data ($data_dir)/ \(reflection/, domains/, templates/, ...\)"
print $" config ($config_dir)/"
print ""
print " next: nu install/config-setup.nu"
}