@arthovlabs/liber 0.5.1 → 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 CHANGED
@@ -57,90 +57,152 @@ LiBER agents are equipped with a first-class toolset:
57
57
 
58
58
  ## 🚀 Quick Start
59
59
 
60
- ### Install from npm
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
- Both commands are installed: `liber` (CLI / TUI) and `liberd` (daemon).
78
+ No global install wanted? `npx @arthovlabs/liber --version` runs it once.
68
79
 
69
- ### Or from source
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
- > **Linux/WSL: use the full setup script** — it handles the pieces `npm link`
79
- > needs sudo for, creates an isolated `.venv` for the Python TUI (textual + httpx),
80
- > and copies `.env` from the template:
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
- pip install rich textual httpx
91
+ bash scripts/setup.sh
92
92
  ```
93
93
 
94
- ### 2. Configuration
94
+ **Windows (PowerShell):**
95
95
 
96
- Create your local `.env`:
97
- ```bash
98
- cp .env.example .env
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
- ### 3. Usage
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
- liber spawn
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
- #### Launch Interactive LiBER Terminal
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
- liber
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
- #### Launch Tactical Cyber-Obsidian TUI Dashboard
119
- ```powershell
120
- liber tui
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
- *Or directly via Python:*
123
- ```powershell
124
- python bin/liber-tui.py
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
- #### One-Shot CLI Execution
128
- ```powershell
129
- liber chat dev-bot "audit project directory and verify package.json"
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
- #### Manage Fleet
133
- ```powershell
134
- liber ps # Check agent fleet status and uptime
135
- liber logs -n 25 # View structured telemetry stream
136
- liber skills # List procedural skills (liber skills <name> to print one)
137
- liber resume # Continue an interrupted turn from .liber/turns/
138
- liber reset # Clear the persisted conversation window
139
- liber bench # Run the 8-case model-compliance benchmark (grounded, honesty, tools)
140
- liber desktop # Launch the LiBER desktop shell (LiBER.exe — OpenCode-style UI)
141
- liber stop # Gracefully shutdown daemon
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` (242 tests: a scripted model that hallucinates, gets
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arthovlabs/liber",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "LiBER — autonomous agent orchestrator with a machine-enforced grounding gate and a skill library",
5
5
  "bin": {
6
6
  "liber": "bin/liber",
@@ -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
- try { return fs.readFileSync(tokenPath(), 'utf8').trim() || null; } catch { return null; }
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
- const existing = readToken();
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(DIR(), { recursive: true });
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
  };
@@ -26,8 +26,11 @@ function isGitRepo() {
26
26
  }
27
27
 
28
28
  function branch() {
29
+ // `rev-parse --abbrev-ref HEAD` prints the literal string "HEAD" on a detached
30
+ // checkout (CI tag builds do this), which is not a branch name — reporting it
31
+ // as one made the UI show a branch called HEAD and marked nothing current.
29
32
  const b = sh('git', ['rev-parse', '--abbrev-ref', 'HEAD']);
30
- if (b && b.trim()) return b.trim();
33
+ if (b && b.trim() && b.trim() !== 'HEAD') return b.trim();
31
34
  const detached = sh('git', ['rev-parse', '--short', 'HEAD']);
32
35
  return detached ? `detached@${detached.trim()}` : null;
33
36
  }
@@ -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) {