@arthovlabs/liber 0.5.2 → 0.5.3
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/README.md +115 -53
- package/bin/liber +17 -1
- package/package.json +1 -1
- package/src/daemon/core/auth.js +30 -4
- package/src/daemon/liberd.js +3 -0
package/README.md
CHANGED
|
@@ -57,90 +57,152 @@ LiBER agents are equipped with a first-class toolset:
|
|
|
57
57
|
|
|
58
58
|
## 🚀 Quick Start
|
|
59
59
|
|
|
60
|
-
###
|
|
60
|
+
### Requirements
|
|
61
|
+
|
|
62
|
+
| Need | Version | For |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| **Node.js** | 18+ (20 recommended) | the CLI, the daemon, every tool |
|
|
65
|
+
| **npm** | ships with Node | install and global link |
|
|
66
|
+
| **Git** | any | source install, workspace/git panels |
|
|
67
|
+
| Python 3.10+ | optional | the `liber tui` dashboard (textual + httpx) |
|
|
68
|
+
| Docker | optional | the container sandbox; without it commands run in Constrained Language Mode |
|
|
69
|
+
| Chrome or Edge | optional | the `browser` tool (launched headless on demand) |
|
|
70
|
+
|
|
71
|
+
### Option A — install from npm (fastest)
|
|
61
72
|
|
|
62
73
|
```bash
|
|
63
74
|
npm i -g @arthovlabs/liber
|
|
64
|
-
liber --version
|
|
75
|
+
liber --version # prints the installed version
|
|
65
76
|
```
|
|
66
77
|
|
|
67
|
-
|
|
78
|
+
No global install wanted? `npx @arthovlabs/liber --version` runs it once.
|
|
68
79
|
|
|
69
|
-
###
|
|
80
|
+
### Option B — install from GitHub (source)
|
|
70
81
|
|
|
71
82
|
```bash
|
|
72
83
|
git clone https://github.com/june-arthov/LiBER-.git
|
|
73
84
|
cd LiBER-
|
|
74
|
-
npm install
|
|
75
|
-
npm link
|
|
76
85
|
```
|
|
77
86
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
>
|
|
82
|
-
> ```bash
|
|
83
|
-
> bash scripts/setup.sh
|
|
84
|
-
> ```
|
|
85
|
-
>
|
|
86
|
-
> Manual fallback: `sudo npm link` (or run `node bin/liber` without linking) and
|
|
87
|
-
> `python3 -m pip install --user textual httpx` for the TUI.
|
|
88
|
-
|
|
89
|
-
*(Optional Python TUI dependencies)*
|
|
87
|
+
**Linux / macOS / WSL — one command does the lot** (node deps, global link, an
|
|
88
|
+
isolated `.venv` for the Python TUI, and `.env` from the template):
|
|
89
|
+
|
|
90
90
|
```bash
|
|
91
|
-
|
|
91
|
+
bash scripts/setup.sh
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
**Windows (PowerShell):**
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
Add your API key (e.g. Ollama Cloud or OpenAI compatible):
|
|
101
|
-
```env
|
|
102
|
-
OLLAMA_CLOUD_API_KEY=your_key_here
|
|
96
|
+
```powershell
|
|
97
|
+
npm install
|
|
98
|
+
npm link # puts `liber` and `liberd` on PATH
|
|
99
|
+
Copy-Item .env.example .env
|
|
103
100
|
```
|
|
104
101
|
|
|
105
|
-
|
|
102
|
+
If `liber` is still "not recognized", npm's global prefix is not on PATH —
|
|
103
|
+
`npm config get prefix` prints it, and its bin directory is what needs adding.
|
|
104
|
+
|
|
105
|
+
**Manual fallback on Linux/WSL** when `npm link` hits `EACCES`:
|
|
106
|
+
`sudo npm link`, or skip linking entirely and run `node bin/liber`.
|
|
107
|
+
TUI dependencies by hand: `python3 -m pip install --user textual httpx`.
|
|
108
|
+
|
|
109
|
+
### Option C — Windows installer (Program Files, with a wizard)
|
|
110
|
+
|
|
111
|
+
From a source checkout, stage the payload once and then run the wizard:
|
|
106
112
|
|
|
107
|
-
#### Start the Background Daemon
|
|
108
113
|
```powershell
|
|
109
|
-
|
|
114
|
+
powershell -ExecutionPolicy Bypass -File installer\build.ps1 # bundles the app + Node runtime
|
|
115
|
+
installer\LiBER-Setup.cmd # double-click works too
|
|
110
116
|
```
|
|
111
117
|
|
|
112
|
-
|
|
118
|
+
The wizard asks for an install folder (default `C:\Program Files\LiBER`) and a
|
|
119
|
+
data folder (default `%LOCALAPPDATA%\LiBER`), then:
|
|
120
|
+
|
|
121
|
+
- copies the app, its dependencies and the desktop shell into Program Files,
|
|
122
|
+
- keeps **your data** — sessions, memory, vault, logs, `.env` — in your own profile,
|
|
123
|
+
so a read-only program directory never blocks the agent,
|
|
124
|
+
- creates Start Menu and desktop shortcuts that start the daemon and then the window,
|
|
125
|
+
- registers **Apps & features** → uninstall (or run `uninstall.ps1` directly),
|
|
126
|
+
- offers to launch LiBER when it finishes.
|
|
127
|
+
|
|
128
|
+
Silent install, for a script or a lab machine:
|
|
129
|
+
|
|
113
130
|
```powershell
|
|
114
|
-
|
|
131
|
+
powershell -ExecutionPolicy Bypass -File installer\LiBER-Setup.ps1 -Silent `
|
|
132
|
+
-InstallDir "C:\Program Files\LiBER" -DataDir "$env:LOCALAPPDATA\LiBER" -NoLaunch
|
|
115
133
|
```
|
|
116
|
-
*Available slash commands inside the terminal: `/help`, `/status`, `/tools`, `/model`, `/logs`, `/clear`, `/tui`, `/exit`.*
|
|
117
134
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
135
|
+
`build.ps1 -SkipNode` leaves the 90 MB Node runtime out of the payload — the
|
|
136
|
+
launcher then uses whatever `node` is on PATH, and says so if there is none.
|
|
137
|
+
|
|
138
|
+
### 1. Give it a provider key
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
cp .env.example .env # Windows: Copy-Item .env.example .env
|
|
121
142
|
```
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
143
|
+
|
|
144
|
+
Open `.env` and fill in the key for the provider you use — only one is needed:
|
|
145
|
+
|
|
146
|
+
```env
|
|
147
|
+
OLLAMA_CLOUD_API_KEY=... # Ollama Cloud (https://ollama.com/v1)
|
|
148
|
+
#OPENAI_API_KEY=sk-... # any OpenAI-compatible vendor
|
|
149
|
+
#ANTHROPIC_API_KEY=sk-ant-...
|
|
150
|
+
#GEMINI_API_KEY=AIza...
|
|
125
151
|
```
|
|
126
152
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
153
|
+
Keys can also be stored in the encrypted vault and managed from the desktop shell
|
|
154
|
+
(Settings → Providers / Vault & Secrets); a stored key wins over the environment.
|
|
155
|
+
|
|
156
|
+
### 2. Start the daemon and prove it works
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
liber spawn # background daemon on 127.0.0.1:4567
|
|
160
|
+
liber ps # fleet status — this must answer, not hang
|
|
161
|
+
liber chat dev-bot "reply with exactly: ready"
|
|
130
162
|
```
|
|
131
163
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
liber
|
|
139
|
-
liber
|
|
140
|
-
liber desktop
|
|
141
|
-
|
|
164
|
+
The last command is the real smoke test: a turn goes through the model, the
|
|
165
|
+
grounding gate, and comes back with receipts.
|
|
166
|
+
|
|
167
|
+
### 3. Pick a surface
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
liber # interactive CLI
|
|
171
|
+
liber tui # Textual dashboard (needs the Python deps)
|
|
172
|
+
liber desktop # LiBER.exe shell — source install only: the npm package
|
|
173
|
+
# ships the runtime, not desktop/ (build: cd desktop && npx neu build)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
All of them talk to the same daemon, so memory, sessions, budget and the audit
|
|
177
|
+
logs are shared.
|
|
178
|
+
|
|
179
|
+
### Command reference
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
liber spawn # start the daemon in the background
|
|
183
|
+
liber stop # shut it down gracefully
|
|
184
|
+
liber ps # agents, uptime, telemetry
|
|
185
|
+
liber chat dev-bot "..." # one-shot turn
|
|
186
|
+
liber models # list models from the live endpoint
|
|
187
|
+
liber model <name> # switch the active model
|
|
188
|
+
liber logs -n 25 # structured telemetry stream
|
|
189
|
+
liber skills [name] # list or print a procedural skill
|
|
190
|
+
liber resume [agentId] # continue an interrupted turn
|
|
191
|
+
liber reset # clear the persisted conversation window
|
|
192
|
+
liber bench # run the model-compliance benchmark
|
|
142
193
|
```
|
|
143
194
|
|
|
195
|
+
### If something goes wrong
|
|
196
|
+
|
|
197
|
+
| Symptom | Cause and fix |
|
|
198
|
+
|---|---|
|
|
199
|
+
| `liber: command not found` | npm's global prefix is not on PATH — `npm config get prefix`, add its bin dir |
|
|
200
|
+
| `EACCES` during `npm link` (Linux/WSL) | system node dir — `sudo npm link`, or run `node bin/liber` |
|
|
201
|
+
| `liber ps` hangs or refuses | the daemon is not running — `liber spawn`; the token it writes is `.liber/daemon.token` |
|
|
202
|
+
| `401` from a turn | no provider key resolved — check `.env`, or store it in the vault |
|
|
203
|
+
| `liber tui` exits immediately | Python deps missing — `pip install textual httpx` |
|
|
204
|
+
| Container backend silently absent | Docker daemon down — `GET /sandbox` says so, and commands fall back to Constrained Language Mode |
|
|
205
|
+
|
|
144
206
|
---
|
|
145
207
|
|
|
146
208
|
## 🧿 Zero-Hallucination Protocol
|
|
@@ -173,7 +235,7 @@ the cited receipt actually entails it. It is still not a proof system: the seman
|
|
|
173
235
|
subtle unsupported claims or flag supported ones, and a verifier outage fails open (recorded as
|
|
174
236
|
`semantic: error`, never silently as `pass`).
|
|
175
237
|
|
|
176
|
-
Run the enforcement suite with `npm test` (
|
|
238
|
+
Run the enforcement suite with `npm test` (296 cases at the time of writing: a scripted model that hallucinates, gets
|
|
177
239
|
blocked, and is forced to read the file before answering; a 15-case grounding regression dataset;
|
|
178
240
|
the terminal policy incl. per-stage pipeline evaluation; the approval-channel wiring; the budget
|
|
179
241
|
guard; and the git/workspace surface). CI runs the suite on Linux and Windows via
|
package/bin/liber
CHANGED
|
@@ -225,10 +225,26 @@ async function getPrimaryAgent() {
|
|
|
225
225
|
return 'dev-bot';
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
// A bare 401 from a directory without a token is the most common first-run
|
|
229
|
+
// confusion, so say what to do instead of repeating the server sentence.
|
|
230
|
+
function authHint(message = '') {
|
|
231
|
+
if (!/unauthori[sz]ed/i.test(String(message))) return '';
|
|
232
|
+
const token = auth.readToken();
|
|
233
|
+
return [
|
|
234
|
+
'',
|
|
235
|
+
' No daemon token usable from this directory.',
|
|
236
|
+
' • start a daemon here: liber spawn',
|
|
237
|
+
' • the token lives in: .liber/daemon.token (workspace)',
|
|
238
|
+
' ~/.liber/daemon.token (machine copy)',
|
|
239
|
+
' • or point at another daemon: set LIBER_TOKEN=<token>',
|
|
240
|
+
token ? '' : ' • nothing was found in either place just now.'
|
|
241
|
+
].filter(Boolean).join('\n');
|
|
242
|
+
}
|
|
243
|
+
|
|
228
244
|
// Render formatted status table
|
|
229
245
|
async function formatStatusTable() {
|
|
230
246
|
const res = await requestDaemon('/ps');
|
|
231
|
-
if (!res.success) throw new Error(res.error);
|
|
247
|
+
if (!res.success) throw new Error(res.error + authHint(res.error));
|
|
232
248
|
|
|
233
249
|
const lines = [];
|
|
234
250
|
lines.push('');
|
package/package.json
CHANGED
package/src/daemon/core/auth.js
CHANGED
|
@@ -183,15 +183,41 @@ function rotate(id) {
|
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
/* ── legacy helpers, kept so existing callers keep working ────────────────── */
|
|
186
|
+
/** Machine-level copy, so a globally installed CLI can reach this daemon. */
|
|
187
|
+
const machineTokenPath = () => path.join(require('os').homedir(), '.liber', 'daemon.token');
|
|
188
|
+
|
|
189
|
+
/** Copy the workspace token to the machine path (0600), best effort. */
|
|
190
|
+
function publishMachineToken(token = readWorkspaceToken()) {
|
|
191
|
+
if (!token) return null;
|
|
192
|
+
try {
|
|
193
|
+
const file = machineTokenPath();
|
|
194
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
195
|
+
fs.writeFileSync(file, token, { encoding: 'utf8', mode: 0o600 });
|
|
196
|
+
return file;
|
|
197
|
+
} catch { return null; }
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function readWorkspaceToken() {
|
|
201
|
+
try { return fs.readFileSync(tokenPath(), 'utf8').trim() || null; } catch { return null; }
|
|
202
|
+
}
|
|
203
|
+
|
|
186
204
|
function readToken() {
|
|
187
205
|
if (process.env.LIBER_TOKEN) return process.env.LIBER_TOKEN.trim();
|
|
188
|
-
|
|
206
|
+
// Workspace first (a daemon per project), then the machine copy written by
|
|
207
|
+
// `liber spawn` — without the fallback, a globally installed CLI run in a
|
|
208
|
+
// fresh directory gets a bare 401 with no hint of what to do.
|
|
209
|
+
const local = readWorkspaceToken();
|
|
210
|
+
if (local) return local;
|
|
211
|
+
try { return fs.readFileSync(machineTokenPath(), 'utf8').trim() || null; } catch { return null; }
|
|
189
212
|
}
|
|
190
213
|
function ensureToken() {
|
|
191
|
-
|
|
214
|
+
// The daemon must create its *workspace* token. Using readToken() here would
|
|
215
|
+
// adopt the machine-level copy and never write .liber/daemon.token, so two
|
|
216
|
+
// workspaces would end up sharing one token.
|
|
217
|
+
const existing = readWorkspaceToken();
|
|
192
218
|
if (existing) return existing;
|
|
193
219
|
const token = crypto.randomBytes(32).toString('hex');
|
|
194
|
-
fs.mkdirSync(
|
|
220
|
+
fs.mkdirSync(path.dirname(tokenPath()), { recursive: true });
|
|
195
221
|
fs.writeFileSync(tokenPath(), token, { encoding: 'utf8', mode: 0o600 });
|
|
196
222
|
return token;
|
|
197
223
|
}
|
|
@@ -213,5 +239,5 @@ function isAuthorized(req, expected) {
|
|
|
213
239
|
module.exports = {
|
|
214
240
|
bootstrap, verify, touch, rateLimit, can, scopeFor, audit, create, list, revoke, rotate, hash,
|
|
215
241
|
SCOPES, DEFAULT_LIMIT, FILE, AUDIT,
|
|
216
|
-
tokenPath, readToken, ensureToken, extract, safeEqual, isAuthorized
|
|
242
|
+
tokenPath, machineTokenPath, readToken, readWorkspaceToken, publishMachineToken, ensureToken, extract, safeEqual, isAuthorized
|
|
217
243
|
};
|
package/src/daemon/liberd.js
CHANGED
|
@@ -195,6 +195,9 @@ class LiberDaemon {
|
|
|
195
195
|
llmManager.initialize(config.agent.engine);
|
|
196
196
|
policy.configure(config.policy || {});
|
|
197
197
|
this.token = auth.ensureToken();
|
|
198
|
+
// A globally installed CLI may be run from any directory; give it a way to
|
|
199
|
+
// find this daemon without hunting for the workspace token.
|
|
200
|
+
auth.publishMachineToken(this.token);
|
|
198
201
|
|
|
199
202
|
this.maxLoops = config.limits?.circuit_breaker?.max_loops || 12;
|
|
200
203
|
if (config.memory?.persist !== false) {
|