@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/src/cli/ui.js
ADDED
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
// src/cli/ui.js — Liber Agy-Style Terminal Interface
|
|
2
|
+
const readline = require('readline');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
|
|
6
|
+
// Single source of truth for branding: never hardcode a version in the UI.
|
|
7
|
+
const pkg = require('../../package.json');
|
|
8
|
+
const VERSION = pkg.version || '0.0.0';
|
|
9
|
+
|
|
10
|
+
// Terminal capability detection: default to rich colors unless explicitly disabled with NO_COLOR
|
|
11
|
+
const noColor = process.env.NO_COLOR !== undefined;
|
|
12
|
+
|
|
13
|
+
// Color and styling engine with TrueColor + 256-color VT100 fallbacks
|
|
14
|
+
const C = {
|
|
15
|
+
// Brand colors
|
|
16
|
+
cyan: (s) => (noColor ? s : `\x1b[38;2;6;182;212m${s}\x1b[0m`),
|
|
17
|
+
sky: (s) => (noColor ? s : `\x1b[38;2;56;189;248m${s}\x1b[0m`),
|
|
18
|
+
blue: (s) => (noColor ? s : `\x1b[38;2;59;130;246m${s}\x1b[0m`),
|
|
19
|
+
purple: (s) => (noColor ? s : `\x1b[38;2;168;85;247m${s}\x1b[0m`),
|
|
20
|
+
green: (s) => (noColor ? s : `\x1b[38;2;16;185;129m${s}\x1b[0m`),
|
|
21
|
+
amber: (s) => (noColor ? s : `\x1b[38;2;245;158;11m${s}\x1b[0m`),
|
|
22
|
+
gold: (s) => (noColor ? s : `\x1b[38;2;229;178;92m${s}\x1b[0m`),
|
|
23
|
+
red: (s) => (noColor ? s : `\x1b[38;2;239;68;68m${s}\x1b[0m`),
|
|
24
|
+
dim: (s) => (noColor ? s : `\x1b[38;2;148;163;184m${s}\x1b[0m`),
|
|
25
|
+
darkDim: (s) => (noColor ? s : `\x1b[38;2;71;85;105m${s}\x1b[0m`),
|
|
26
|
+
bold: (s) => (noColor ? s : `\x1b[1m${s}\x1b[22m`),
|
|
27
|
+
italic: (s) => (noColor ? s : `\x1b[3m${s}\x1b[23m`),
|
|
28
|
+
underline: (s) => (noColor ? s : `\x1b[4m${s}\x1b[24m`),
|
|
29
|
+
|
|
30
|
+
// Solid background badges / pills
|
|
31
|
+
badgeCyan: (s) => (noColor ? `[${s}]` : `\x1b[48;2;6;182;212m\x1b[38;2;15;23;42m\x1b[1m ${s} \x1b[0m`),
|
|
32
|
+
badgeBlue: (s) => (noColor ? `[${s}]` : `\x1b[48;2;30;64;175m\x1b[38;2;241;245;249m\x1b[1m ${s} \x1b[0m`),
|
|
33
|
+
badgeGreen: (s) => (noColor ? `[${s}]` : `\x1b[48;2;16;185;129m\x1b[38;2;15;23;42m\x1b[1m ${s} \x1b[0m`),
|
|
34
|
+
badgeAmber: (s) => (noColor ? `[${s}]` : `\x1b[48;2;217;119;6m\x1b[38;2;15;23;42m\x1b[1m ${s} \x1b[0m`),
|
|
35
|
+
badgePurple: (s) => (noColor ? `[${s}]` : `\x1b[48;2;147;51;234m\x1b[38;2;241;245;249m\x1b[1m ${s} \x1b[0m`),
|
|
36
|
+
badgeRed: (s) => (noColor ? `[${s}]` : `\x1b[48;2;220;38;38m\x1b[38;2;255;255;255m\x1b[1m ${s} \x1b[0m`),
|
|
37
|
+
badgeDark: (s) => (noColor ? `[${s}]` : `\x1b[48;2;30;41;59m\x1b[38;2;148;163;184m\x1b[1m ${s} \x1b[0m`),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Strip ANSI escape sequences to compute true visual string width
|
|
41
|
+
function stripAnsi(str) {
|
|
42
|
+
return String(str).replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Window width helper
|
|
46
|
+
function getCols() {
|
|
47
|
+
return Math.min(process.stdout.columns || 80, 100);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Format a single box row with pixel-perfect right border alignment
|
|
51
|
+
function formatBoxRow(content, innerWidth) {
|
|
52
|
+
const visibleLen = stripAnsi(content).length;
|
|
53
|
+
const pad = Math.max(0, innerWidth - visibleLen);
|
|
54
|
+
return ' ' + C.darkDim('│') + ' ' + content + ' '.repeat(pad) + ' ' + C.darkDim('│');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Modern LiBER Terminal Header Banner
|
|
58
|
+
function printAgyBanner(info = {}) {
|
|
59
|
+
const width = Math.max(getCols(), 76);
|
|
60
|
+
const innerWidth = width - 2;
|
|
61
|
+
const agent = info.agent || 'dev-bot';
|
|
62
|
+
const model = info.model || 'glm-5.3-flash:cloud';
|
|
63
|
+
const port = info.port || 4567;
|
|
64
|
+
const status = info.online ? C.green('● Online') : C.amber('○ Offline');
|
|
65
|
+
const cwd = process.cwd();
|
|
66
|
+
const displayCwd = cwd.length > (innerWidth - 8) ? '...' + cwd.slice(-(innerWidth - 11)) : cwd;
|
|
67
|
+
|
|
68
|
+
const titleBar = C.cyan(C.bold(` LIBER v${VERSION} `));
|
|
69
|
+
const titleLen = stripAnsi(titleBar).length;
|
|
70
|
+
const topBar = C.darkDim(' ╭─') + titleBar + C.darkDim('─'.repeat(Math.max(0, innerWidth - titleLen)) + '─╮');
|
|
71
|
+
const bottomBar = C.darkDim(' ╰─') + C.darkDim('─'.repeat(innerWidth) + '─╯');
|
|
72
|
+
|
|
73
|
+
const label = C.gold('◢') + C.bold(' L i B E R ') + C.gold('◣') + C.dim(' Autonomous AI Agent Orchestrator');
|
|
74
|
+
const ver = C.purple(`v${VERSION}`);
|
|
75
|
+
const padLen = Math.max(0, innerWidth - stripAnsi(label).length - stripAnsi(ver).length);
|
|
76
|
+
const row1 = label + ' '.repeat(padLen) + ver;
|
|
77
|
+
const host = info.host || '127.0.0.1';
|
|
78
|
+
const row2 = C.dim('Agent: ') + C.bold(C.sky(agent)) + ' ' + C.dim('Model: ') + C.amber(model) + ' ' + C.dim('Daemon: ') + C.dim(`${host}:${port}`) + ' [' + status + ']';
|
|
79
|
+
const row3 = C.dim('Dir: ') + C.dim(displayCwd);
|
|
80
|
+
|
|
81
|
+
console.log();
|
|
82
|
+
console.log(topBar);
|
|
83
|
+
console.log(formatBoxRow(row1, innerWidth));
|
|
84
|
+
console.log(formatBoxRow(row2, innerWidth));
|
|
85
|
+
console.log(formatBoxRow(row3, innerWidth));
|
|
86
|
+
console.log(bottomBar);
|
|
87
|
+
console.log(
|
|
88
|
+
' ' + C.dim('Type prompt to begin, or ') +
|
|
89
|
+
C.cyan('/help') + C.dim(' for commands (') +
|
|
90
|
+
C.dim('/status, /tools, /model, /logs, /tui, /clear, /exit') + C.dim(')')
|
|
91
|
+
);
|
|
92
|
+
console.log();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Spinner for agent thought process
|
|
96
|
+
function startSpinner(label = 'Thinking...') {
|
|
97
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
98
|
+
let i = 0;
|
|
99
|
+
const startTime = Date.now();
|
|
100
|
+
let timer = null;
|
|
101
|
+
|
|
102
|
+
if (process.stdout.isTTY) {
|
|
103
|
+
process.stdout.write(` ${C.cyan(frames[0])} ${C.dim(label)}`);
|
|
104
|
+
timer = setInterval(() => {
|
|
105
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
106
|
+
process.stdout.write(`\r\x1b[2K ${C.cyan(frames[i++ % frames.length])} ${C.dim(label)} ${C.darkDim(`(${elapsed}s)`)}`);
|
|
107
|
+
}, 80);
|
|
108
|
+
} else {
|
|
109
|
+
console.log(` ${C.cyan('◇')} ${C.dim(label)}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
stop() {
|
|
114
|
+
if (timer) {
|
|
115
|
+
clearInterval(timer);
|
|
116
|
+
process.stdout.write('\r\x1b[2K');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Markdown formatter with syntax boxes and highlights
|
|
123
|
+
function formatMarkdown(text) {
|
|
124
|
+
const lines = String(text).split('\n');
|
|
125
|
+
const result = [];
|
|
126
|
+
let inCode = false;
|
|
127
|
+
let codeLang = '';
|
|
128
|
+
const width = Math.max(getCols(), 70);
|
|
129
|
+
|
|
130
|
+
for (let line of lines) {
|
|
131
|
+
const trimmed = line.trim();
|
|
132
|
+
|
|
133
|
+
// Code fence toggle
|
|
134
|
+
if (trimmed.startsWith('```')) {
|
|
135
|
+
if (!inCode) {
|
|
136
|
+
inCode = true;
|
|
137
|
+
codeLang = trimmed.slice(3).trim() || 'code';
|
|
138
|
+
result.push(C.darkDim(' ╭─ ') + C.blue(C.bold(codeLang)) + ' ' + C.darkDim('─'.repeat(Math.max(0, width - codeLang.length - 10))));
|
|
139
|
+
} else {
|
|
140
|
+
inCode = false;
|
|
141
|
+
result.push(C.darkDim(' ╰─' + '─'.repeat(Math.max(0, width - 8))));
|
|
142
|
+
}
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (inCode) {
|
|
147
|
+
result.push(C.darkDim(' │ ') + C.amber(line));
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Markdown Headers
|
|
152
|
+
if (/^###\s+/.test(trimmed)) {
|
|
153
|
+
result.push('\n ' + C.bold(C.sky('■ ' + trimmed.replace(/^###\s+/, ''))));
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (/^##\s+/.test(trimmed)) {
|
|
157
|
+
result.push('\n ' + C.bold(C.cyan('▲ ' + trimmed.replace(/^##\s+/, ''))));
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (/^#\s+/.test(trimmed)) {
|
|
161
|
+
result.push('\n ' + C.bold(C.cyan('◆ ' + trimmed.replace(/^#\s+/, ''))));
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Bullet points
|
|
166
|
+
if (/^[\*\-]\s+/.test(trimmed)) {
|
|
167
|
+
line = ' ' + C.cyan('• ') + line.replace(/^[\*\-]\s+/, '');
|
|
168
|
+
} else {
|
|
169
|
+
line = ' ' + line;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Bold, inline code, links
|
|
173
|
+
line = line
|
|
174
|
+
.replace(/\*\*(.*?)\*\*/g, (_, m) => C.bold(m))
|
|
175
|
+
.replace(/`([^`]+)`/g, (_, m) => C.amber(m))
|
|
176
|
+
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, text, url) => C.cyan(C.underline(text)) + C.dim(` (${url})`));
|
|
177
|
+
|
|
178
|
+
result.push(line);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return result.join('\n');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Render cognitive pulse / reasoning block
|
|
185
|
+
function printThought(thought) {
|
|
186
|
+
const width = Math.max(getCols(), 70);
|
|
187
|
+
console.log(C.darkDim(' ╭─ ') + C.amber('🧠 REASONING ') + C.darkDim('─'.repeat(Math.max(0, width - 22))));
|
|
188
|
+
String(thought).split('\n').forEach(line => {
|
|
189
|
+
console.log(C.darkDim(' │ ') + C.dim(line));
|
|
190
|
+
});
|
|
191
|
+
console.log(C.darkDim(' ╰─' + '─'.repeat(Math.max(0, width - 8))));
|
|
192
|
+
console.log();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Render tool execution card
|
|
196
|
+
function printToolExecution(tool) {
|
|
197
|
+
const width = Math.max(getCols(), 70);
|
|
198
|
+
const toolName = tool.name || (tool.command ? 'terminal' : 'tool');
|
|
199
|
+
const details = tool.command || (tool.args ? JSON.stringify(tool.args) : '');
|
|
200
|
+
console.log(C.darkDim(' ╭─ ') + C.purple(`⚡ Tool: ${toolName} `) + C.darkDim('─'.repeat(Math.max(0, width - toolName.length - 17))));
|
|
201
|
+
if (details) {
|
|
202
|
+
console.log(C.darkDim(' │ ') + C.dim('Args: ') + C.cyan(C.bold(details)));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (tool.result) {
|
|
206
|
+
const out = tool.result.output || (tool.result.error ? C.red(tool.result.error) : JSON.stringify(tool.result));
|
|
207
|
+
const lines = String(out).trim().split('\n').slice(0, 8);
|
|
208
|
+
console.log(C.darkDim(' │ ') + C.dim('Output:'));
|
|
209
|
+
lines.forEach(l => console.log(C.darkDim(' │ ') + C.dim(l)));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const duration = tool.durationMs !== undefined ? ` in ${tool.durationMs}ms` : '';
|
|
213
|
+
console.log(C.darkDim(' │ ') + C.green(`✔ Success${duration}`));
|
|
214
|
+
console.log(C.darkDim(' ╰─' + '─'.repeat(Math.max(0, width - 8))));
|
|
215
|
+
console.log();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Render agent answer
|
|
219
|
+
function printAgentResponse(agent, responseData) {
|
|
220
|
+
const isString = typeof responseData === 'string';
|
|
221
|
+
const text = isString ? responseData : responseData.response || '';
|
|
222
|
+
const trace = (!isString && responseData.trace) || [];
|
|
223
|
+
const durationMs = (!isString && responseData.durationMs) || null;
|
|
224
|
+
const iterations = (!isString && responseData.iterations) || null;
|
|
225
|
+
|
|
226
|
+
// Print execution traces (thoughts & tool calls)
|
|
227
|
+
for (const step of trace) {
|
|
228
|
+
if (step.type === 'thought' && step.content) {
|
|
229
|
+
printThought(step.content);
|
|
230
|
+
} else if (step.type === 'tool') {
|
|
231
|
+
printToolExecution(step);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Header for agent response
|
|
236
|
+
console.log(' ' + C.badgeCyan(agent.toUpperCase()) + ' ' + C.dim(new Date().toTimeString().slice(0, 8)));
|
|
237
|
+
console.log();
|
|
238
|
+
console.log(formatMarkdown(text));
|
|
239
|
+
console.log();
|
|
240
|
+
|
|
241
|
+
// Metrics footer
|
|
242
|
+
const grounding = (!isString && responseData.grounding) || null;
|
|
243
|
+
const stats = [];
|
|
244
|
+
if (durationMs) stats.push(`took ${(durationMs / 1000).toFixed(1)}s`);
|
|
245
|
+
if (iterations) stats.push(`${iterations} iteration${iterations > 1 ? 's' : ''}`);
|
|
246
|
+
let statsColor = C.darkDim;
|
|
247
|
+
if (grounding) {
|
|
248
|
+
if (grounding.status === 'PASS') {
|
|
249
|
+
const receipts = (grounding.ledger && grounding.ledger.receipts) || [];
|
|
250
|
+
stats.push(`grounded · ${receipts.length} receipt${receipts.length === 1 ? '' : 's'}`);
|
|
251
|
+
statsColor = C.green;
|
|
252
|
+
} else if (grounding.status === 'REPAIR') {
|
|
253
|
+
stats.push(`⚠ ${grounding.violations.length} unverified claim${grounding.violations.length === 1 ? '' : 's'}`);
|
|
254
|
+
statsColor = C.amber;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (stats.length) {
|
|
258
|
+
console.log(' ' + statsColor(`[${stats.join(' · ')}]`));
|
|
259
|
+
console.log();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Print only the answer (used when the stream already rendered reasoning and tools)
|
|
264
|
+
function printAnswerOnly(agent, responseData) {
|
|
265
|
+
printAgentResponse(agent, { ...responseData, trace: [] });
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function truncateText(value, max = 400) {
|
|
269
|
+
const s = String(value == null ? '' : value).replace(/\r/g, '').trim();
|
|
270
|
+
return s.length > max ? s.slice(0, max) + '…' : s;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Live DRAFT preview (raw model output, not yet audited)
|
|
274
|
+
let draftBuffer = '';
|
|
275
|
+
|
|
276
|
+
function renderDraftPreview() {
|
|
277
|
+
if (!process.stdout.isTTY) return;
|
|
278
|
+
const clean = draftBuffer.replace(/\s+/g, ' ');
|
|
279
|
+
const limit = Math.max(40, getCols() - 10);
|
|
280
|
+
const tail = clean.length > limit ? '…' + clean.slice(-limit) : clean;
|
|
281
|
+
process.stdout.write('\r\x1b[2K ' + C.purple('✎ draft ') + C.dim(tail));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Render a daemon SSE event live in the terminal
|
|
285
|
+
function printStreamEvent(event, data = {}) {
|
|
286
|
+
switch (event) {
|
|
287
|
+
case 'draft_start':
|
|
288
|
+
draftBuffer = '';
|
|
289
|
+
renderDraftPreview();
|
|
290
|
+
break;
|
|
291
|
+
|
|
292
|
+
case 'draft':
|
|
293
|
+
draftBuffer += data.delta || '';
|
|
294
|
+
renderDraftPreview();
|
|
295
|
+
break;
|
|
296
|
+
|
|
297
|
+
case 'draft_end':
|
|
298
|
+
if (process.stdout.isTTY) process.stdout.write('\r\x1b[2K');
|
|
299
|
+
if (data.failed) {
|
|
300
|
+
console.log(' ' + C.amber('✎ draft interrupted — falling back to a complete, audited completion'));
|
|
301
|
+
} else if (draftBuffer.length) {
|
|
302
|
+
console.log(' ' + C.darkDim(`✎ draft · ${draftBuffer.length} chars streamed · UNVERIFIED until the grounding audit passes`));
|
|
303
|
+
}
|
|
304
|
+
draftBuffer = '';
|
|
305
|
+
break;
|
|
306
|
+
|
|
307
|
+
case 'thought':
|
|
308
|
+
if (data.content) printThought(data.content);
|
|
309
|
+
break;
|
|
310
|
+
|
|
311
|
+
case 'tool_dispatch': {
|
|
312
|
+
const name = data.tool || 'tool';
|
|
313
|
+
const detail = data.command || JSON.stringify(data.args || {});
|
|
314
|
+
console.log(' ' + C.purple('⚡ ' + name) + ' ' + C.dim(truncateText(detail, 160)));
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
case 'observation': {
|
|
319
|
+
const out = String(data.output || '').trim();
|
|
320
|
+
const all = out.split('\n');
|
|
321
|
+
const ok = data.ok !== false;
|
|
322
|
+
console.log(' ' + (ok ? C.green('✓ ') : C.red('✗ ')) + C.dim(`[${data.evidence || '?'}] ${data.tool || ''}${data.exit_code !== undefined ? ` · exit ${data.exit_code}` : ''}`));
|
|
323
|
+
all.slice(0, 6).forEach(line => console.log(' ' + C.darkDim('│ ') + C.dim(truncateText(line, 160))));
|
|
324
|
+
if (all.length > 6) console.log(' ' + C.darkDim('│ ') + C.dim(`… ${all.length - 6} more line(s)`));
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
case 'grounding_audit': {
|
|
329
|
+
const codes = [...new Set((data.violations || []).map(v => v.code))].join(', ');
|
|
330
|
+
console.log(' ' + C.amber(`⚠ grounding: blocked ${(data.violations || []).length} unverified claim(s) — repair ${data.attempt}/${data.max_repairs}`) + C.dim(codes ? ` · ${codes}` : ''));
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
case 'grounding_warning':
|
|
335
|
+
console.log(' ' + C.red(`⚠ unverified: ${(data.violations || []).length} claim(s) shipped with a GROUNDING AUDIT footer`));
|
|
336
|
+
break;
|
|
337
|
+
|
|
338
|
+
case 'loop_guard':
|
|
339
|
+
console.log(' ' + C.amber(`↻ duplicate ${data.tool} call suppressed (receipt ${data.receipt || 'n/a'})`));
|
|
340
|
+
break;
|
|
341
|
+
|
|
342
|
+
case 'subagent_start':
|
|
343
|
+
console.log(' ' + C.cyan(`⇢ subagent ${data.id}`) + C.dim(` ${truncateText(data.goal, 120)}`));
|
|
344
|
+
break;
|
|
345
|
+
|
|
346
|
+
case 'subagent_end':
|
|
347
|
+
console.log(' ' + (data.grounded === false ? C.amber('⇠ subagent') : C.green('⇠ subagent')) + C.dim(` ${data.id} · ${data.status || 'done'}`));
|
|
348
|
+
break;
|
|
349
|
+
|
|
350
|
+
case 'hitl_intercept':
|
|
351
|
+
console.log(' ' + C.amber(`⚠ HITL: auto-approving ${data.tool} (use the TUI for interactive approval)`));
|
|
352
|
+
break;
|
|
353
|
+
|
|
354
|
+
case 'error':
|
|
355
|
+
console.log(' ' + C.red(`✖ ${data.error || 'stream error'}`));
|
|
356
|
+
break;
|
|
357
|
+
|
|
358
|
+
default:
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
function startAgySession({ agent, port, ask, commands, getAgentInfo }) {
|
|
363
|
+
let activeAgent = agent || 'dev-bot';
|
|
364
|
+
let activeModel = 'glm-5.3-flash:cloud';
|
|
365
|
+
let isOnline = true;
|
|
366
|
+
|
|
367
|
+
// Load config details if available
|
|
368
|
+
try {
|
|
369
|
+
const configPath = path.resolve(process.cwd(), process.env.LIBER_CONFIG || 'liber.yaml');
|
|
370
|
+
if (fs.existsSync(configPath)) {
|
|
371
|
+
const yaml = require('js-yaml');
|
|
372
|
+
const doc = yaml.load(fs.readFileSync(configPath, 'utf8'));
|
|
373
|
+
if (doc?.agent?.name) activeAgent = doc.agent.name;
|
|
374
|
+
if (doc?.agent?.engine?.model) activeModel = doc.agent.engine.model;
|
|
375
|
+
}
|
|
376
|
+
} catch { /* use defaults */ }
|
|
377
|
+
|
|
378
|
+
printAgyBanner({ agent: activeAgent, model: activeModel, port, online: isOnline });
|
|
379
|
+
|
|
380
|
+
const rl = readline.createInterface({
|
|
381
|
+
input: process.stdin,
|
|
382
|
+
output: process.stdout,
|
|
383
|
+
terminal: true,
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
let isBusy = false;
|
|
387
|
+
let messageCount = 0;
|
|
388
|
+
|
|
389
|
+
function renderPrompt() {
|
|
390
|
+
const promptText = `\n${C.darkDim('╭─')} ${C.sky(C.bold(activeAgent))} ${C.darkDim('·')} ${C.darkDim('~/Liber')}\n${C.darkDim('╰─')}${C.green(C.bold('❯'))} `;
|
|
391
|
+
rl.setPrompt(promptText);
|
|
392
|
+
rl.prompt(true);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Help command display
|
|
396
|
+
function showHelp() {
|
|
397
|
+
const width = Math.max(getCols(), 70);
|
|
398
|
+
console.log();
|
|
399
|
+
console.log(C.darkDim(' ╭─ ') + C.cyan(C.bold('AVAILABLE COMMANDS')) + ' ' + C.darkDim('─'.repeat(Math.max(0, width - 26))));
|
|
400
|
+
const list = [
|
|
401
|
+
['/help', 'Display this command reference'],
|
|
402
|
+
['/status, /ps', 'Show real-time agent metrics and health'],
|
|
403
|
+
['/tools', 'List registered tool capabilities'],
|
|
404
|
+
['/models', 'List all available AI models from the API endpoint'],
|
|
405
|
+
['/model <name>', 'Switch the active AI model dynamically on the fly'],
|
|
406
|
+
['/logs [n]', 'View recent telemetry events from liber.log'],
|
|
407
|
+
['/clear', 'Clear screen and refresh LiBER banner'],
|
|
408
|
+
['/tui', 'Launch the Tactical Cyber-Obsidian TUI dashboard'],
|
|
409
|
+
['/exit, /quit', 'Exit the LiBER Terminal'],
|
|
410
|
+
];
|
|
411
|
+
|
|
412
|
+
list.forEach(([cmd, desc]) => {
|
|
413
|
+
console.log(' ' + C.darkDim('│ ') + C.sky(cmd.padEnd(16)) + C.dim(desc));
|
|
414
|
+
});
|
|
415
|
+
console.log(C.darkDim(' ╰─' + '─'.repeat(Math.max(0, width - 8))));
|
|
416
|
+
console.log();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
rl.on('line', async (line) => {
|
|
420
|
+
if (isBusy) return;
|
|
421
|
+
const input = line.trim();
|
|
422
|
+
if (!input) {
|
|
423
|
+
renderPrompt();
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Handle slash commands
|
|
428
|
+
if (input.startsWith('/')) {
|
|
429
|
+
const parts = input.slice(1).split(/\s+/);
|
|
430
|
+
const cmd = parts[0].toLowerCase();
|
|
431
|
+
const arg = parts.slice(1).join(' ');
|
|
432
|
+
|
|
433
|
+
if (cmd === 'exit' || cmd === 'quit') {
|
|
434
|
+
console.log(C.dim('\n Goodbye from Liber. 👋\n'));
|
|
435
|
+
process.exit(0);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (cmd === 'clear') {
|
|
439
|
+
console.clear();
|
|
440
|
+
printAgyBanner({ agent: activeAgent, model: activeModel, port, online: isOnline });
|
|
441
|
+
renderPrompt();
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (cmd === 'help') {
|
|
446
|
+
showHelp();
|
|
447
|
+
renderPrompt();
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (cmd === 'tui') {
|
|
452
|
+
console.log(C.cyan('\n Launching Tactical Cyber-Obsidian TUI Dashboard...\n'));
|
|
453
|
+
const { spawn } = require('child_process');
|
|
454
|
+
const pythonTui = path.join(__dirname, '..', '..', 'bin', 'liber-tui.py');
|
|
455
|
+
if (fs.existsSync(pythonTui)) {
|
|
456
|
+
spawn('python', [pythonTui], { stdio: 'inherit' });
|
|
457
|
+
} else {
|
|
458
|
+
spawn(process.execPath, [path.join(__dirname, '..', '..', 'bin', 'liber-tui')], { stdio: 'inherit' });
|
|
459
|
+
}
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (commands && commands[cmd]) {
|
|
464
|
+
try {
|
|
465
|
+
const res = await commands[cmd](arg);
|
|
466
|
+
if (res) console.log(res);
|
|
467
|
+
} catch (err) {
|
|
468
|
+
console.log(` ${C.red('✖ Error:')} ${err.message}`);
|
|
469
|
+
}
|
|
470
|
+
renderPrompt();
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
console.log(` ${C.red('✖ Unknown command:')} ${C.dim(input)}. Type ${C.cyan('/help')} for list.`);
|
|
475
|
+
renderPrompt();
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Regular conversation input
|
|
480
|
+
isBusy = true;
|
|
481
|
+
console.log();
|
|
482
|
+
const spinner = startSpinner('Cognitive Pulse thinking...');
|
|
483
|
+
|
|
484
|
+
try {
|
|
485
|
+
const data = await ask(input);
|
|
486
|
+
spinner.stop();
|
|
487
|
+
messageCount++;
|
|
488
|
+
printAgentResponse(activeAgent, data);
|
|
489
|
+
} catch (err) {
|
|
490
|
+
spinner.stop();
|
|
491
|
+
console.log(` ${C.red('✖ Error:')} ${err.message}\n`);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
isBusy = false;
|
|
495
|
+
renderPrompt();
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
rl.on('SIGINT', () => {
|
|
499
|
+
console.log(C.dim('\n\n Exiting Liber. Bye! 👋\n'));
|
|
500
|
+
process.exit(0);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
rl.on('close', () => {
|
|
504
|
+
console.log(C.dim('\n Session closed.\n'));
|
|
505
|
+
process.exit(0);
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
renderPrompt();
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
module.exports = {
|
|
512
|
+
startAgySession,
|
|
513
|
+
printAgyBanner,
|
|
514
|
+
formatMarkdown,
|
|
515
|
+
printAgentResponse,
|
|
516
|
+
printAnswerOnly,
|
|
517
|
+
printStreamEvent,
|
|
518
|
+
printThought,
|
|
519
|
+
C
|
|
520
|
+
};
|