251 lines
8.4 KiB
HTML
251 lines
8.4 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Self-Healing Autopoietic Shell</title>
|
|
<style>
|
|
:root{
|
|
--bg:#0b0d10; --panel:#12151a; --ink:#e6e9ef; --dim:#8b93a1;
|
|
--accent:#7c9eff; --ok:#5fd88f; --warn:#f2b64b; --err:#ff6b6b;
|
|
--border:#232833;
|
|
}
|
|
*{box-sizing:border-box;}
|
|
body{
|
|
margin:0; background:var(--bg); color:var(--ink);
|
|
font:15px/1.55 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
}
|
|
header{
|
|
padding:2.5rem 1.5rem 1.25rem; max-width:900px; margin:0 auto;
|
|
}
|
|
h1{font-size:1.4rem; margin:0 0 .4rem; font-weight:600;}
|
|
header p{color:var(--dim); margin:.2rem 0; font-size:.92rem;}
|
|
main{max-width:900px; margin:0 auto; padding:0 1.5rem 3rem; display:grid; gap:1.25rem; grid-template-columns:1fr; }
|
|
@media (min-width:760px){ main{grid-template-columns: 1.3fr .9fr;} }
|
|
.panel{
|
|
background:var(--panel); border:1px solid var(--border); border-radius:10px;
|
|
overflow:hidden;
|
|
}
|
|
.panel h2{
|
|
font-size:.78rem; text-transform:uppercase; letter-spacing:.06em; color:var(--dim);
|
|
margin:0; padding:.7rem .9rem; border-bottom:1px solid var(--border);
|
|
}
|
|
#term{
|
|
padding:.9rem; height:360px; overflow-y:auto; white-space:pre-wrap; word-break:break-word;
|
|
font-size:.88rem;
|
|
}
|
|
#term .line{margin:0 0 .15rem;}
|
|
.prompt{color:var(--accent);}
|
|
.learn{color:var(--warn);}
|
|
.ok{color:var(--ok);}
|
|
.err{color:var(--err);}
|
|
.dim{color:var(--dim);}
|
|
form{display:flex; border-top:1px solid var(--border);}
|
|
form span{padding:.6rem .9rem; color:var(--accent);}
|
|
input{
|
|
flex:1; background:transparent; border:0; color:var(--ink); font:inherit;
|
|
padding:.6rem .9rem .6rem 0; outline:none;
|
|
}
|
|
#pfile{padding:.9rem; min-height:120px; font-size:.85rem;}
|
|
#pfile .row{color:var(--ok); margin:0 0 .3rem;}
|
|
#pfile .empty{color:var(--dim);}
|
|
.try{padding:.9rem; border-top:1px solid var(--border); display:flex; flex-wrap:wrap; gap:.4rem;}
|
|
.try button{
|
|
background:#171b22; border:1px solid var(--border); color:var(--ink);
|
|
font:inherit; font-size:.78rem; padding:.35rem .6rem; border-radius:6px; cursor:pointer;
|
|
}
|
|
.try button:hover{border-color:var(--accent); color:var(--accent);}
|
|
code{background:#171b22; padding:.1rem .35rem; border-radius:4px; font-size:.85em;}
|
|
pre{
|
|
background:#171b22; padding:.9rem; border-radius:8px; overflow-x:auto;
|
|
font-size:.82rem; border:1px solid var(--border); margin:.8rem 1.5rem 0; max-width:900px;
|
|
}
|
|
footer{max-width:900px; margin:0 auto; padding:0 1.5rem 3rem; color:var(--dim); font-size:.82rem;}
|
|
footer a{color:var(--accent);}
|
|
.reset{color:var(--dim); font-size:.78rem; background:none; border:0; cursor:pointer; text-decoration:underline; padding:0;}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<header>
|
|
<h1>🔁 Self-Healing Autopoietic Shell</h1>
|
|
<p>A shell function that learns from its own failures — run a command, teach it a fix when it fails, and it never fails that way again.</p>
|
|
<p class="dim">This is a live simulation of <code>f()</code> running entirely in your browser. Try a command below.</p>
|
|
</header>
|
|
|
|
<main>
|
|
<div class="panel">
|
|
<h2>Simulated shell</h2>
|
|
<div id="term"></div>
|
|
<form id="form">
|
|
<span>$ f</span>
|
|
<input id="cmdInput" autocomplete="off" placeholder="cat needs-fix.txt" autofocus>
|
|
</form>
|
|
<div class="try">
|
|
<button data-cmd="cat needs-fix.txt">cat needs-fix.txt</button>
|
|
<button data-cmd="curl api.example.com">curl api.example.com</button>
|
|
<button data-cmd="echo already-fine">echo already-fine</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="panel">
|
|
<h2>p <span class="dim" style="text-transform:none;">(learned fixes)</span></h2>
|
|
<div id="pfile"><div class="empty">// empty — nothing learned yet</div></div>
|
|
<div class="try">
|
|
<button class="reset" id="resetBtn">reset memory</button>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<pre>f() {
|
|
FIX=""
|
|
if [ -f p ]; then
|
|
while IFS= read -r line; do
|
|
case "$line" in
|
|
"$*="*) FIX="${line#"$*="}" ;;
|
|
esac
|
|
done < p
|
|
fi
|
|
until { eval "$FIX" 2>/dev/null; "$@"; }; do
|
|
printf '\n[LEARN] "%s" failed. Enter a fix (blank to give up): ' "$*" >&2
|
|
read -r n
|
|
[ -n "$n" ] || return 1
|
|
echo "$*=$n" >> p
|
|
FIX="$n"
|
|
done
|
|
}</pre>
|
|
|
|
<footer>
|
|
Real source, tests, and design notes are in the repo alongside this page —
|
|
see <code>src/f.sh</code>, <code>test/test_f.sh</code>, and <code>docs/DESIGN.md</code>.
|
|
</footer>
|
|
|
|
<script>
|
|
(function(){
|
|
const term = document.getElementById('term');
|
|
const form = document.getElementById('form');
|
|
const input = document.getElementById('cmdInput');
|
|
const pfileEl = document.getElementById('pfile');
|
|
const resetBtn = document.getElementById('resetBtn');
|
|
|
|
// "p" — the learned-fixes store. Simulated filesystem state lives here.
|
|
let p = {}; // command -> fix
|
|
let fs = {}; // simulated tiny filesystem for the demo commands
|
|
let awaitingFix = null; // command string we're currently learning a fix for
|
|
|
|
function print(text, cls){
|
|
const div = document.createElement('div');
|
|
div.className = 'line' + (cls ? ' ' + cls : '');
|
|
div.textContent = text;
|
|
term.appendChild(div);
|
|
term.scrollTop = term.scrollHeight;
|
|
}
|
|
|
|
function renderP(){
|
|
const keys = Object.keys(p);
|
|
if(!keys.length){
|
|
pfileEl.innerHTML = '<div class="empty">// empty — nothing learned yet</div>';
|
|
return;
|
|
}
|
|
pfileEl.innerHTML = keys.map(k =>
|
|
`<div class="row">${escapeHtml(k)}=${escapeHtml(p[k])}</div>`
|
|
).join('');
|
|
}
|
|
|
|
function escapeHtml(s){
|
|
return s.replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
}
|
|
|
|
// Applies a learned fix (or a freshly supplied one) to the tiny simulated fs/state.
|
|
function applyFix(fix){
|
|
const m = fix.match(/^echo\s+(.*?)\s*>\s*(\S+)$/);
|
|
if(m){
|
|
let val = m[1].trim();
|
|
if((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))){
|
|
val = val.slice(1,-1);
|
|
}
|
|
fs[m[2]] = val;
|
|
return;
|
|
}
|
|
if(/^(export\s+)?API_KEY=/.test(fix)){ fs['__api_key'] = true; return; }
|
|
}
|
|
|
|
// Runs one of the demo commands against simulated state. Returns {ok, out}.
|
|
function runCommand(cmd){
|
|
if(cmd === 'cat needs-fix.txt'){
|
|
if(fs['needs-fix.txt'] !== undefined) return {ok:true, out: fs['needs-fix.txt']};
|
|
return {ok:false, out: 'cat: needs-fix.txt: No such file or directory'};
|
|
}
|
|
if(cmd === 'curl api.example.com'){
|
|
if(fs['__api_key']) return {ok:true, out: '{"status":"ok"}'};
|
|
return {ok:false, out: 'curl: (401) Unauthorized — missing API_KEY'};
|
|
}
|
|
if(cmd.startsWith('echo ')){
|
|
return {ok:true, out: cmd.slice(5)};
|
|
}
|
|
return {ok:false, out: cmd + ': command not found'};
|
|
}
|
|
|
|
function tryCommand(cmd){
|
|
if(p[cmd]) applyFix(p[cmd]);
|
|
const res = runCommand(cmd);
|
|
if(res.ok){
|
|
print(res.out, 'ok');
|
|
return;
|
|
}
|
|
print(res.out, 'err');
|
|
print(`[LEARN] "${cmd}" failed. Enter a fix (blank to give up):`, 'learn');
|
|
awaitingFix = cmd;
|
|
}
|
|
|
|
form.addEventListener('submit', function(e){
|
|
e.preventDefault();
|
|
const val = input.value;
|
|
input.value = '';
|
|
if(awaitingFix){
|
|
print('> ' + (val || '(blank)'), 'dim');
|
|
if(!val.trim()){
|
|
print(`f: giving up on "${awaitingFix}"`, 'err');
|
|
awaitingFix = null;
|
|
return;
|
|
}
|
|
p[awaitingFix] = val.trim();
|
|
renderP();
|
|
applyFix(val.trim());
|
|
const cmd = awaitingFix;
|
|
awaitingFix = null;
|
|
const res = runCommand(cmd);
|
|
print(res.ok ? res.out : res.out, res.ok ? 'ok' : 'err');
|
|
if(!res.ok){
|
|
print(`[LEARN] "${cmd}" failed. Enter a fix (blank to give up):`, 'learn');
|
|
awaitingFix = cmd;
|
|
}
|
|
return;
|
|
}
|
|
if(!val.trim()) return;
|
|
print('$ f ' + val, 'prompt');
|
|
tryCommand(val.trim());
|
|
});
|
|
|
|
document.querySelectorAll('.try button[data-cmd]').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
input.value = btn.dataset.cmd;
|
|
input.focus();
|
|
});
|
|
});
|
|
|
|
resetBtn.addEventListener('click', () => {
|
|
p = {}; fs = {}; awaitingFix = null;
|
|
renderP();
|
|
term.innerHTML = '';
|
|
print('// memory reset — p is empty again', 'dim');
|
|
});
|
|
|
|
print('// try: cat needs-fix.txt', 'dim');
|
|
print('// it will fail once — teach it a fix like: echo hello > needs-fix.txt', 'dim');
|
|
})();
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|