@arthovlabs/liber 0.0.0-stage → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +348 -2
- package/bin/liber +706 -0
- package/bin/liber-tui +244 -0
- package/bin/liber-tui.py +825 -0
- package/bin/liberd-entry +17 -0
- package/liber.yaml +84 -0
- package/package.json +43 -4
- package/resources/icons/appIcon.png +0 -0
- package/resources/icons/appIcon.svg +20 -0
- package/resources/icons/appIcon@512.png +0 -0
- package/resources/icons/atom-transparent-256.png +0 -0
- package/resources/icons/atom-transparent.png +0 -0
- package/resources/icons/atom-transparent.svg +19 -0
- package/resources/icons/make-atom.js +168 -0
- package/src/cli/ui.js +520 -0
- package/src/daemon/core/auth.js +217 -0
- package/src/daemon/core/automations.js +130 -0
- package/src/daemon/core/budget.js +145 -0
- package/src/daemon/core/circuit-breaker.js +31 -0
- package/src/daemon/core/config-apply.js +223 -0
- package/src/daemon/core/connectors.js +131 -0
- package/src/daemon/core/engine.js +1029 -0
- package/src/daemon/core/evidence.js +306 -0
- package/src/daemon/core/executor.js +10 -0
- package/src/daemon/core/grounding.js +420 -0
- package/src/daemon/core/injection.js +24 -0
- package/src/daemon/core/journal.js +81 -0
- package/src/daemon/core/liber-soul.js +157 -0
- package/src/daemon/core/lock.js +32 -0
- package/src/daemon/core/memory.js +106 -0
- package/src/daemon/core/model-switch.js +58 -0
- package/src/daemon/core/policy.js +102 -0
- package/src/daemon/core/providers.js +503 -0
- package/src/daemon/core/registry.js +356 -0
- package/src/daemon/core/secrets.js +35 -0
- package/src/daemon/core/skills.js +264 -0
- package/src/daemon/core/tools/browser-tool.js +280 -0
- package/src/daemon/core/tools/file-tools.js +277 -0
- package/src/daemon/core/tools/memory-tool.js +127 -0
- package/src/daemon/core/tools/skill-tool.js +44 -0
- package/src/daemon/core/tools/task-tool.js +41 -0
- package/src/daemon/core/tools/terminal-tool.js +449 -0
- package/src/daemon/core/tools/todo-tool.js +100 -0
- package/src/daemon/core/tools/web-tool.js +181 -0
- package/src/daemon/core/trace.js +40 -0
- package/src/daemon/core/validate.js +105 -0
- package/src/daemon/core/vault.js +158 -0
- package/src/daemon/core/verifier.js +98 -0
- package/src/daemon/core/voice.js +83 -0
- package/src/daemon/core/workspace.js +199 -0
- package/src/daemon/liberd.js +1343 -0
- package/src/daemon/llm/adapters/anthropic-adapter.js +190 -0
- package/src/daemon/llm/adapters/gemini-adapter.js +148 -0
- package/src/daemon/llm/adapters/ollama-adapter.js +74 -0
- package/src/daemon/llm/adapters/openai-adapter.js +148 -0
- package/src/daemon/llm/base.js +31 -0
- package/src/daemon/llm/manager.js +134 -0
- package/src/utils/config-loader.js +109 -0
- package/src/utils/logger.js +56 -0
package/bin/liber-tui
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// bin/liber-tui — Liber dashboard (blessed)
|
|
3
|
+
const blessed = require('blessed');
|
|
4
|
+
const http = require('http');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
const PORT = parseInt(process.env.LIBER_PORT || '4567', 10);
|
|
9
|
+
const LOG_FILE = path.join(process.cwd(), 'liber.log');
|
|
10
|
+
|
|
11
|
+
// ── Palette: named ANSI colors only (works in every terminal) ───────
|
|
12
|
+
const P = {
|
|
13
|
+
text: 'white', dim: 'gray', cyan: 'lightcyan', blue: 'lightblue', green: 'lightgreen',
|
|
14
|
+
yellow: 'lightyellow', red: 'lightred', purple: 'lightmagenta',
|
|
15
|
+
};
|
|
16
|
+
const esc = (s) => blessed.escape(String(s));
|
|
17
|
+
const fg = (c, s) => `{${c}-fg}${s}{/}`;
|
|
18
|
+
// solid colored "pill": dark text on a bright background — visible everywhere
|
|
19
|
+
const badge = (bg, s) => `{black-fg}{${bg}-bg}{bold} ${s} {/bold}{/}`;
|
|
20
|
+
|
|
21
|
+
const screen = blessed.screen({
|
|
22
|
+
smartCSR: true, fullUnicode: true, terminal: 'xterm-256color', forceUnicode: true, title: 'LIBER',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// ── Header: solid gradient-ish bar ──────────────────────────────────
|
|
26
|
+
const header = blessed.box({ top: 0, left: 0, width: '100%', height: 3, tags: true, style: { bg: 'blue' } });
|
|
27
|
+
blessed.box({
|
|
28
|
+
parent: header, top: 1, left: 1, height: 1, width: 'shrink', tags: true,
|
|
29
|
+
content: `{white-fg}{blue-bg}{bold}${' ◢◤ L I B E R ◥◣ '}{/bold}{/}{lightcyan-fg}{blue-bg} Agent Orchestrator{/}`,
|
|
30
|
+
});
|
|
31
|
+
const statusBox = blessed.box({ parent: header, top: 1, right: 1, height: 1, width: 'shrink', tags: true, style: { bg: 'blue' } });
|
|
32
|
+
blessed.line({ parent: header, top: 0, left: 0, width: '100%', orientation: 'horizontal', style: { bg: 'blue' } });
|
|
33
|
+
|
|
34
|
+
// ── Chat ────────────────────────────────────────────────────────────
|
|
35
|
+
const chat = blessed.log({
|
|
36
|
+
top: 3, left: 0, bottom: 4, width: '68%', tags: true, label: badge(P.cyan, '◆ CONVERSATION'),
|
|
37
|
+
border: { type: 'line' }, padding: { left: 1, right: 1 },
|
|
38
|
+
scrollable: true, alwaysScroll: true, mouse: true, keys: true, vi: true,
|
|
39
|
+
scrollbar: { ch: '█', style: { fg: 'cyan' } },
|
|
40
|
+
style: { border: { fg: 'cyan' } },
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// ── Sidebar ─────────────────────────────────────────────────────────
|
|
44
|
+
const agentCard = blessed.box({
|
|
45
|
+
top: 3, left: '68%', width: '32%', height: 10, tags: true, label: badge(P.green, '● AGENT'),
|
|
46
|
+
border: { type: 'line' }, padding: { left: 1 }, style: { border: { fg: 'green' } },
|
|
47
|
+
});
|
|
48
|
+
const metrics = blessed.box({
|
|
49
|
+
top: 13, left: '68%', width: '32%', height: 8, tags: true, label: badge(P.yellow, '▲ LATENCY'),
|
|
50
|
+
border: { type: 'line' }, padding: { left: 1 }, style: { border: { fg: 'yellow' } },
|
|
51
|
+
});
|
|
52
|
+
const activity = blessed.log({
|
|
53
|
+
top: 21, left: '68%', width: '32%', bottom: 4, tags: true, label: badge(P.purple, '≡ ACTIVITY'),
|
|
54
|
+
border: { type: 'line' }, padding: { left: 1 }, scrollable: true, style: { border: { fg: 'magenta' } },
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// ── Input + footer ──────────────────────────────────────────────────
|
|
58
|
+
const inputBox = blessed.box({
|
|
59
|
+
bottom: 1, left: 0, width: '100%', height: 3, tags: true, border: { type: 'line' },
|
|
60
|
+
style: { border: { fg: 'lightblue' } },
|
|
61
|
+
});
|
|
62
|
+
blessed.box({ parent: inputBox, top: 0, left: 0, width: 5, height: 1, tags: true, content: badge(P.green, '❯') });
|
|
63
|
+
const input = blessed.textbox({
|
|
64
|
+
parent: inputBox, top: 0, left: 6, right: 1, height: 1, inputOnFocus: true, style: { fg: 'white' },
|
|
65
|
+
});
|
|
66
|
+
const footerKeys = [['Enter', 'send', 'green'], ['PgUp/PgDn', 'scroll', 'cyan'], ['Ctrl+L', 'clear', 'yellow'], ['Ctrl+C', 'quit', 'red']];
|
|
67
|
+
const footer = blessed.box({
|
|
68
|
+
bottom: 0, left: 0, width: '100%', height: 1, tags: true,
|
|
69
|
+
content: footerKeys.map(([k, d, c]) => `${badge(c, k)}${fg(P.dim, ' ' + d + ' ')}`).join(''),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
[header, chat, agentCard, metrics, activity, inputBox, footer].forEach((w) => screen.append(w));
|
|
73
|
+
|
|
74
|
+
// ── State & helpers ─────────────────────────────────────────────────
|
|
75
|
+
const state = { online: false, agent: null, latencies: [], busy: false, frame: 0, sent: 0 };
|
|
76
|
+
const SPARK = '▁▂▃▄▅▆▇█';
|
|
77
|
+
const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
78
|
+
|
|
79
|
+
function request(route, method = 'GET', body = null) {
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
const req = http.request({ hostname: '127.0.0.1', port: PORT, path: route, method, headers: { 'Content-Type': 'application/json' }, timeout: 120000 }, (res) => {
|
|
82
|
+
let d = ''; res.on('data', (c) => (d += c));
|
|
83
|
+
res.on('end', () => { try { resolve(JSON.parse(d)); } catch { reject(new Error('Invalid response')); } });
|
|
84
|
+
});
|
|
85
|
+
req.on('timeout', () => req.destroy(new Error('Timeout')));
|
|
86
|
+
req.on('error', reject);
|
|
87
|
+
if (body) req.write(JSON.stringify(body));
|
|
88
|
+
req.end();
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const stamp = () => new Date().toTimeString().slice(0, 8);
|
|
93
|
+
const fmtUp = (s) => `${String(Math.floor(s / 3600)).padStart(2, '0')}:${String(Math.floor(s / 60) % 60).padStart(2, '0')}:${String(s % 60).padStart(2, '0')}`;
|
|
94
|
+
|
|
95
|
+
function say(who, text) {
|
|
96
|
+
const [color, tag] = who === 'user' ? [P.green, 'YOU'] : who === 'error' ? [P.red, 'ERROR'] : [P.cyan, String(who).toUpperCase()];
|
|
97
|
+
const bar = fg(color, '▌ ');
|
|
98
|
+
chat.add(`${badge(color, tag)} ${fg(P.dim, stamp())}`);
|
|
99
|
+
let inCode = false;
|
|
100
|
+
for (const raw of String(text).split('\n')) {
|
|
101
|
+
if (raw.trim().startsWith('```')) {
|
|
102
|
+
inCode = !inCode;
|
|
103
|
+
chat.add(bar + fg(P.dim, inCode ? '╭─ ' + esc(raw.trim().slice(3) || 'code') : '╰─'));
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (inCode) { chat.add(bar + fg(P.dim, '│ ') + fg(P.yellow, esc(raw))); continue; }
|
|
107
|
+
chat.add(bar + esc(raw)
|
|
108
|
+
.replace(/\*\*(.+?)\*\*/g, (_, b) => `{bold}${b}{/bold}`)
|
|
109
|
+
.replace(/`([^`]+)`/g, (_, k) => fg(P.yellow, k)));
|
|
110
|
+
}
|
|
111
|
+
chat.add('');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function welcome() {
|
|
115
|
+
const logo = [
|
|
116
|
+
'██╗ ██╗██████╗ ███████╗██████╗ ',
|
|
117
|
+
'██║ ██║██╔══██╗██╔════╝██╔══██╗',
|
|
118
|
+
'██║ ██║██████╔╝█████╗ ██████╔╝',
|
|
119
|
+
'██║ ██║██╔══██╗██╔══╝ ██╔══██╗',
|
|
120
|
+
'███████╗██║██████╔╝███████╗██║ ██║',
|
|
121
|
+
'╚══════╝╚═╝╚═════╝ ╚══════╝╚═╝ ╚═╝',
|
|
122
|
+
];
|
|
123
|
+
const cols = [P.cyan, P.cyan, P.blue, P.blue, P.purple, P.purple];
|
|
124
|
+
chat.add('');
|
|
125
|
+
logo.forEach((l, i) => chat.add(' {bold}' + fg(cols[i], l) + '{/bold}'));
|
|
126
|
+
chat.add('');
|
|
127
|
+
chat.add(' ' + fg(P.dim, 'Minimalist-Utilitarian AI Agent Orchestrator'));
|
|
128
|
+
chat.add(' ' + badge(P.green, 'Enter') + fg(P.dim, ' send message ') + badge(P.cyan, 'PgUp') + fg(P.dim, ' scroll'));
|
|
129
|
+
chat.add('');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function renderHeader() {
|
|
133
|
+
const dot = state.online ? '{lightgreen-fg}●{/}' : '{lightred-fg}●{/}';
|
|
134
|
+
const label = state.online ? '{lightgreen-fg}online{/}' : '{lightred-fg}offline{/}';
|
|
135
|
+
statusBox.setContent(`{blue-bg}${dot}{blue-bg} ${label}{white-fg}{blue-bg} :${PORT} │ ${stamp()} {/}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const bar = (frac, w, color) => {
|
|
139
|
+
const n = Math.max(0, Math.min(w, Math.round(frac * w)));
|
|
140
|
+
return fg(color, '█'.repeat(n)) + fg(P.dim, '░'.repeat(w - n));
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
function renderSide(agent) {
|
|
144
|
+
if (!agent) {
|
|
145
|
+
agentCard.setContent(`\n${fg(P.red, '✖ Daemon unreachable')}\n\n${fg(P.dim, 'Run:')}\n ${badge(P.yellow, 'liber spawn')}`);
|
|
146
|
+
} else {
|
|
147
|
+
const up = Math.floor((Date.now() - agent.startTime) / 1000);
|
|
148
|
+
const row = (k, v) => `${fg(P.dim, k.padEnd(9))}${v}`;
|
|
149
|
+
agentCard.setContent([
|
|
150
|
+
'', `{bold}${fg(P.cyan, '◆ ' + esc(agent.id))}{/bold}`, '',
|
|
151
|
+
row('status', fg(P.green, '● ' + agent.status)),
|
|
152
|
+
row('uptime', fmtUp(up)),
|
|
153
|
+
row('requests', fg(P.cyan, agent.metrics.requests)),
|
|
154
|
+
row('model', esc(agent.config?.engine?.model || '-')),
|
|
155
|
+
].join('\n'));
|
|
156
|
+
}
|
|
157
|
+
const L = state.latencies.slice(-24);
|
|
158
|
+
if (!L.length) metrics.setContent(`\n${fg(P.dim, 'no data yet')}`);
|
|
159
|
+
else {
|
|
160
|
+
const max = Math.max(...L), avg = L.reduce((a, b) => a + b, 0) / L.length, last = L[L.length - 1];
|
|
161
|
+
const spark = L.map((v) => SPARK[Math.min(7, Math.floor((v / (max || 1)) * 7))]).join('');
|
|
162
|
+
metrics.setContent([
|
|
163
|
+
'', fg(P.yellow, spark), '',
|
|
164
|
+
`${fg(P.dim, 'avg ')}${bar(avg / (max || 1), 8, P.green)} ${(avg / 1000).toFixed(1)}s`,
|
|
165
|
+
`${fg(P.dim, 'last')}${bar(last / (max || 1), 8, P.cyan)} ${(last / 1000).toFixed(1)}s`,
|
|
166
|
+
].join('\n'));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function renderInputLabel() {
|
|
171
|
+
inputBox.setLabel(state.busy
|
|
172
|
+
? badge(P.yellow, `${FRAMES[state.frame % FRAMES.length]} thinking…`)
|
|
173
|
+
: badge(P.blue, `message for ${state.agent || 'agent'}`));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function poll() {
|
|
177
|
+
try {
|
|
178
|
+
const res = await request('/ps');
|
|
179
|
+
state.online = !!res.success;
|
|
180
|
+
const a = res.data && res.data[0];
|
|
181
|
+
if (a) state.agent = a.id;
|
|
182
|
+
renderSide(a);
|
|
183
|
+
} catch { state.online = false; renderSide(null); }
|
|
184
|
+
renderHeader();
|
|
185
|
+
renderInputLabel();
|
|
186
|
+
screen.render();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let logPos = 0;
|
|
190
|
+
function tailLog() {
|
|
191
|
+
try {
|
|
192
|
+
const st = fs.statSync(LOG_FILE);
|
|
193
|
+
if (logPos === 0) logPos = Math.max(0, st.size - 2000);
|
|
194
|
+
if (st.size < logPos) logPos = 0;
|
|
195
|
+
if (st.size > logPos) {
|
|
196
|
+
const fd = fs.openSync(LOG_FILE, 'r'); const buf = Buffer.alloc(st.size - logPos);
|
|
197
|
+
fs.readSync(fd, buf, 0, buf.length, logPos); fs.closeSync(fd); logPos = st.size;
|
|
198
|
+
for (const l of buf.toString('utf8').split('\n').filter(Boolean)) {
|
|
199
|
+
try {
|
|
200
|
+
const e = JSON.parse(l);
|
|
201
|
+
const col = e.level === 'ERROR' ? P.red : e.level === 'WARN' ? P.yellow : P.green;
|
|
202
|
+
activity.add(`${fg(col, '●')} ${fg(P.dim, e.timestamp.slice(11, 19))} ${esc(e.event)}`);
|
|
203
|
+
} catch { /* partial line */ }
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
} catch { /* no log yet */ }
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ── Input handling ──────────────────────────────────────────────────
|
|
210
|
+
input.on('submit', async (value) => {
|
|
211
|
+
const text = (value || '').trim();
|
|
212
|
+
input.clearValue();
|
|
213
|
+
if (!text || state.busy) { input.readInput(); return screen.render(); }
|
|
214
|
+
say('user', text);
|
|
215
|
+
state.busy = true;
|
|
216
|
+
const t0 = Date.now();
|
|
217
|
+
try {
|
|
218
|
+
if (!state.agent) throw new Error('Daemon not running (liber spawn)');
|
|
219
|
+
const res = await request('/chat', 'POST', { agentId: state.agent, message: text });
|
|
220
|
+
if (!res.success) throw new Error(res.error);
|
|
221
|
+
state.latencies.push(Date.now() - t0); state.sent++;
|
|
222
|
+
say(state.agent, res.response);
|
|
223
|
+
} catch (e) { say('error', e.message); }
|
|
224
|
+
state.busy = false;
|
|
225
|
+
poll(); tailLog();
|
|
226
|
+
input.readInput();
|
|
227
|
+
screen.render();
|
|
228
|
+
});
|
|
229
|
+
input.on('cancel', () => input.readInput());
|
|
230
|
+
|
|
231
|
+
screen.key(['C-c'], () => process.exit(0));
|
|
232
|
+
input.key(['C-c'], () => process.exit(0));
|
|
233
|
+
screen.key(['C-l'], () => { chat.setContent(''); screen.render(); });
|
|
234
|
+
screen.key(['pageup'], () => { chat.scroll(-Math.floor(chat.height / 2)); screen.render(); });
|
|
235
|
+
screen.key(['pagedown'], () => { chat.scroll(Math.floor(chat.height / 2)); screen.render(); });
|
|
236
|
+
|
|
237
|
+
setInterval(() => { if (state.busy) { state.frame++; renderInputLabel(); screen.render(); } }, 90);
|
|
238
|
+
setInterval(() => { poll(); tailLog(); }, 2000);
|
|
239
|
+
|
|
240
|
+
welcome();
|
|
241
|
+
renderInputLabel(); renderHeader();
|
|
242
|
+
poll(); tailLog();
|
|
243
|
+
input.readInput();
|
|
244
|
+
screen.render();
|