@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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +348 -2
  3. package/bin/liber +706 -0
  4. package/bin/liber-tui +244 -0
  5. package/bin/liber-tui.py +825 -0
  6. package/bin/liberd-entry +17 -0
  7. package/liber.yaml +84 -0
  8. package/package.json +43 -4
  9. package/resources/icons/appIcon.png +0 -0
  10. package/resources/icons/appIcon.svg +20 -0
  11. package/resources/icons/appIcon@512.png +0 -0
  12. package/resources/icons/atom-transparent-256.png +0 -0
  13. package/resources/icons/atom-transparent.png +0 -0
  14. package/resources/icons/atom-transparent.svg +19 -0
  15. package/resources/icons/make-atom.js +168 -0
  16. package/src/cli/ui.js +520 -0
  17. package/src/daemon/core/auth.js +217 -0
  18. package/src/daemon/core/automations.js +130 -0
  19. package/src/daemon/core/budget.js +145 -0
  20. package/src/daemon/core/circuit-breaker.js +31 -0
  21. package/src/daemon/core/config-apply.js +223 -0
  22. package/src/daemon/core/connectors.js +131 -0
  23. package/src/daemon/core/engine.js +1029 -0
  24. package/src/daemon/core/evidence.js +306 -0
  25. package/src/daemon/core/executor.js +10 -0
  26. package/src/daemon/core/grounding.js +420 -0
  27. package/src/daemon/core/injection.js +24 -0
  28. package/src/daemon/core/journal.js +81 -0
  29. package/src/daemon/core/liber-soul.js +157 -0
  30. package/src/daemon/core/lock.js +32 -0
  31. package/src/daemon/core/memory.js +106 -0
  32. package/src/daemon/core/model-switch.js +58 -0
  33. package/src/daemon/core/policy.js +102 -0
  34. package/src/daemon/core/providers.js +503 -0
  35. package/src/daemon/core/registry.js +356 -0
  36. package/src/daemon/core/secrets.js +35 -0
  37. package/src/daemon/core/skills.js +264 -0
  38. package/src/daemon/core/tools/browser-tool.js +280 -0
  39. package/src/daemon/core/tools/file-tools.js +277 -0
  40. package/src/daemon/core/tools/memory-tool.js +127 -0
  41. package/src/daemon/core/tools/skill-tool.js +44 -0
  42. package/src/daemon/core/tools/task-tool.js +41 -0
  43. package/src/daemon/core/tools/terminal-tool.js +449 -0
  44. package/src/daemon/core/tools/todo-tool.js +100 -0
  45. package/src/daemon/core/tools/web-tool.js +181 -0
  46. package/src/daemon/core/trace.js +40 -0
  47. package/src/daemon/core/validate.js +105 -0
  48. package/src/daemon/core/vault.js +158 -0
  49. package/src/daemon/core/verifier.js +98 -0
  50. package/src/daemon/core/voice.js +83 -0
  51. package/src/daemon/core/workspace.js +199 -0
  52. package/src/daemon/liberd.js +1343 -0
  53. package/src/daemon/llm/adapters/anthropic-adapter.js +190 -0
  54. package/src/daemon/llm/adapters/gemini-adapter.js +148 -0
  55. package/src/daemon/llm/adapters/ollama-adapter.js +74 -0
  56. package/src/daemon/llm/adapters/openai-adapter.js +148 -0
  57. package/src/daemon/llm/base.js +31 -0
  58. package/src/daemon/llm/manager.js +134 -0
  59. package/src/utils/config-loader.js +109 -0
  60. 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();