expressible 0.1.2 → 0.1.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
@@ -1,15 +1,13 @@
1
1
  # Expressible CLI
2
2
 
3
- Open-source developer tools by [Expressible AI, Inc.](https://expressible.ai)
3
+ Train small, local text classifiers from labeled examples. No API keys, no cloud, no Python, no GPU. Your data never leaves your machine.
4
4
 
5
- The Expressible CLI provides local-first tooling for AI workflows where data sovereignty matters. It is part of the [Expressible platform](https://gen.expressible.ai) for secure, end-to-end software delivery.
5
+ Open source (Apache 2.0) · Built by [Expressible AI](https://expressible.ai)
6
6
 
7
7
  ---
8
8
 
9
9
  ## Distill
10
10
 
11
- Train small, task-specific ML models from input/output examples. Runs entirely on your machine. Your data never leaves your environment.
12
-
13
11
  ![Expressible Distill demo](https://raw.githubusercontent.com/expressibleai/expressible-cli/refs/heads/main/demo.svg)
14
12
 
15
13
  ```
@@ -136,6 +134,10 @@ Everything.
136
134
 
137
135
  ---
138
136
 
137
+ Works in environments where data can't leave the network — healthcare, financial services, legal, government, or any organization with data residency requirements.
138
+
139
+ ---
140
+
139
141
  ### Use Cases
140
142
 
141
143
  **Legal document review** — Classify contract clauses by type across thousands of agreements. Privileged documents stay within your perimeter.
@@ -160,25 +162,17 @@ With 50 labeled examples (~30 minutes of work), no API keys, and no ML expertise
160
162
  | 20 Newsgroups (5 categories) | 80.0% | [Public dataset](https://huggingface.co/datasets/SetFit/20_newsgroups) |
161
163
  | AG News (4 categories) | 64.0% | [Public dataset](https://huggingface.co/datasets/fancyzhx/ag_news) |
162
164
 
163
- AG News improves to 80% with 100 training samples. More data helps — see [benchmarks](docs/benchmarks.md) for scaling details.
164
-
165
- Public dataset results use real-world text from established ML benchmarks — 50 samples drawn from datasets containing 120,000+ entries. All samples and the test harness are included in the repo so you can reproduce these results:
165
+ Reproduce these results:
166
166
 
167
167
  ```bash
168
168
  npx tsx tests/harness/run.ts
169
169
  ```
170
170
 
171
- Accuracy improves as you add more examples through the review-retrain loop. Full results, methodology, and known limitations: **[docs/benchmarks.md](docs/benchmarks.md)**
172
-
173
- ---
171
+ **Known limitation:** Distill struggles with sentiment and tone classification (44–50% accuracy). The embedding model captures what text is *about*, not how it *evaluates*. "Amazing camera" and "terrible camera" produce nearly identical vectors. Details in [benchmarks](docs/benchmarks.md).
174
172
 
175
- ### Built for Environments Where Data Stays Internal
173
+ AG News improves to 80% with 100 training samples. More data helps — see [benchmarks](docs/benchmarks.md) for scaling details. Public dataset results use real-world text from established ML benchmarks — 50 samples drawn from datasets containing 120,000+ entries.
176
174
 
177
- - Healthcare teams handling patient records
178
- - Financial services processing sensitive transactions
179
- - Government contractors with data residency requirements
180
- - Legal teams working with privileged documents
181
- - Any organization with data sovereignty obligations
175
+ Accuracy improves as you add more examples through the review-retrain loop. Full results, methodology, and known limitations: **[docs/benchmarks.md](docs/benchmarks.md)**
182
176
 
183
177
  ---
184
178
 
@@ -248,9 +242,7 @@ my-task/
248
242
 
249
243
  ---
250
244
 
251
- ### Expressible Platform
252
-
253
- The CLI is the open-source, local-first layer of the Expressible platform. For teams that need governance, traceability, and managed deployment across AI-generated workloads, see [expressible.ai](https://expressible.ai).
245
+ The CLI is fully standalone and open source. [Expressible AI](https://expressible.ai) offers additional tooling for teams that need governance and managed deployment.
254
246
 
255
247
  ### Contributing
256
248
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "expressible",
3
- "version": "0.1.2",
4
- "description": "Open-source CLI toolkit by Expressible AI, Inc.",
3
+ "version": "0.1.3",
4
+ "description": "Train small, local text classifiers from labeled examples. No API keys, no cloud, no GPU.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
@@ -14,8 +14,17 @@
14
14
  "test": "vitest run",
15
15
  "test:watch": "vitest"
16
16
  },
17
- "keywords": ["ml", "local", "training", "cli", "inference"],
17
+ "keywords": ["ml", "local", "training", "cli", "inference", "classification", "nlp", "text-classification", "offline", "on-premise"],
18
18
  "license": "Apache-2.0",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/expressibleai/expressible-cli.git"
22
+ },
23
+ "homepage": "https://expressible.ai",
24
+ "bugs": {
25
+ "url": "https://github.com/expressibleai/expressible-cli/issues"
26
+ },
27
+ "author": "Expressible AI, Inc.",
19
28
  "dependencies": {
20
29
  "@tensorflow/tfjs": "^4.22.0",
21
30
  "@tensorflow/tfjs-node": "^4.22.0",
@@ -1,486 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Distill — Review</title>
7
- <style>
8
- * { margin: 0; padding: 0; box-sizing: border-box; }
9
-
10
- body {
11
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
12
- background: #f5f5f5;
13
- color: #333;
14
- min-height: 100vh;
15
- }
16
-
17
- .header {
18
- background: #fff;
19
- border-bottom: 1px solid #e0e0e0;
20
- padding: 16px 24px;
21
- display: flex;
22
- align-items: center;
23
- justify-content: space-between;
24
- flex-wrap: wrap;
25
- gap: 12px;
26
- }
27
-
28
- .header h1 {
29
- font-size: 20px;
30
- font-weight: 600;
31
- color: #111;
32
- }
33
-
34
- .header h1 span {
35
- color: #6366f1;
36
- }
37
-
38
- .stats-bar {
39
- display: flex;
40
- gap: 20px;
41
- font-size: 13px;
42
- color: #666;
43
- }
44
-
45
- .stats-bar .stat-value {
46
- font-weight: 600;
47
- color: #333;
48
- }
49
-
50
- .progress-container {
51
- background: #fff;
52
- padding: 12px 24px;
53
- border-bottom: 1px solid #e0e0e0;
54
- }
55
-
56
- .progress-bar {
57
- width: 100%;
58
- height: 6px;
59
- background: #e5e7eb;
60
- border-radius: 3px;
61
- overflow: hidden;
62
- }
63
-
64
- .progress-fill {
65
- height: 100%;
66
- background: #6366f1;
67
- border-radius: 3px;
68
- transition: width 0.3s ease;
69
- }
70
-
71
- .progress-text {
72
- font-size: 12px;
73
- color: #999;
74
- margin-top: 4px;
75
- }
76
-
77
- .main {
78
- max-width: 1200px;
79
- margin: 24px auto;
80
- padding: 0 24px;
81
- }
82
-
83
- .panels {
84
- display: grid;
85
- grid-template-columns: 1fr 1fr;
86
- gap: 16px;
87
- margin-bottom: 20px;
88
- }
89
-
90
- @media (max-width: 768px) {
91
- .panels { grid-template-columns: 1fr; }
92
- }
93
-
94
- .panel {
95
- background: #fff;
96
- border: 1px solid #e0e0e0;
97
- border-radius: 8px;
98
- overflow: hidden;
99
- }
100
-
101
- .panel-header {
102
- padding: 12px 16px;
103
- background: #fafafa;
104
- border-bottom: 1px solid #e0e0e0;
105
- font-size: 13px;
106
- font-weight: 600;
107
- text-transform: uppercase;
108
- letter-spacing: 0.5px;
109
- color: #888;
110
- }
111
-
112
- .panel-body {
113
- padding: 16px;
114
- font-size: 14px;
115
- line-height: 1.6;
116
- white-space: pre-wrap;
117
- word-break: break-word;
118
- min-height: 120px;
119
- max-height: 400px;
120
- overflow-y: auto;
121
- }
122
-
123
- .actions {
124
- background: #fff;
125
- border: 1px solid #e0e0e0;
126
- border-radius: 8px;
127
- padding: 20px;
128
- }
129
-
130
- .action-buttons {
131
- display: flex;
132
- gap: 12px;
133
- justify-content: center;
134
- margin-bottom: 16px;
135
- }
136
-
137
- .btn {
138
- padding: 10px 32px;
139
- border: 2px solid transparent;
140
- border-radius: 8px;
141
- font-size: 15px;
142
- font-weight: 600;
143
- cursor: pointer;
144
- transition: all 0.15s ease;
145
- display: flex;
146
- align-items: center;
147
- gap: 8px;
148
- }
149
-
150
- .btn:active { transform: scale(0.97); }
151
-
152
- .btn-approve {
153
- background: #ecfdf5;
154
- color: #059669;
155
- border-color: #a7f3d0;
156
- }
157
- .btn-approve:hover { background: #d1fae5; }
158
- .btn-approve.active { background: #059669; color: #fff; }
159
-
160
- .btn-reject {
161
- background: #fef2f2;
162
- color: #dc2626;
163
- border-color: #fecaca;
164
- }
165
- .btn-reject:hover { background: #fee2e2; }
166
- .btn-reject.active { background: #dc2626; color: #fff; }
167
-
168
- .correction-area {
169
- margin-top: 12px;
170
- display: none;
171
- }
172
-
173
- .correction-area.visible {
174
- display: block;
175
- }
176
-
177
- .correction-area label {
178
- display: block;
179
- font-size: 13px;
180
- font-weight: 500;
181
- color: #666;
182
- margin-bottom: 6px;
183
- }
184
-
185
- .correction-area textarea {
186
- width: 100%;
187
- min-height: 80px;
188
- padding: 10px 12px;
189
- border: 1px solid #d1d5db;
190
- border-radius: 6px;
191
- font-family: 'SF Mono', Monaco, Consolas, monospace;
192
- font-size: 13px;
193
- resize: vertical;
194
- }
195
-
196
- .correction-area textarea:focus {
197
- outline: none;
198
- border-color: #6366f1;
199
- box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
200
- }
201
-
202
- .nav {
203
- display: flex;
204
- justify-content: space-between;
205
- align-items: center;
206
- margin-top: 16px;
207
- }
208
-
209
- .nav-btn {
210
- padding: 8px 20px;
211
- border: 1px solid #d1d5db;
212
- border-radius: 6px;
213
- background: #fff;
214
- font-size: 14px;
215
- cursor: pointer;
216
- color: #374151;
217
- }
218
- .nav-btn:hover { background: #f9fafb; }
219
- .nav-btn:disabled { opacity: 0.4; cursor: not-allowed; }
220
-
221
- .nav-info {
222
- font-size: 14px;
223
- color: #666;
224
- }
225
-
226
- .done-btn {
227
- padding: 8px 20px;
228
- border: 1px solid #6366f1;
229
- border-radius: 6px;
230
- background: #6366f1;
231
- color: #fff;
232
- font-size: 14px;
233
- font-weight: 500;
234
- cursor: pointer;
235
- }
236
- .done-btn:hover { background: #4f46e5; }
237
-
238
- .empty-state {
239
- text-align: center;
240
- padding: 60px 20px;
241
- color: #999;
242
- }
243
-
244
- .keyboard-hint {
245
- text-align: center;
246
- font-size: 12px;
247
- color: #aaa;
248
- margin-top: 10px;
249
- }
250
-
251
- kbd {
252
- padding: 2px 6px;
253
- background: #f3f4f6;
254
- border: 1px solid #d1d5db;
255
- border-radius: 3px;
256
- font-size: 11px;
257
- font-family: inherit;
258
- }
259
- </style>
260
- </head>
261
- <body>
262
- <div class="header">
263
- <h1><span>distill</span> — Review</h1>
264
- <div class="stats-bar" id="stats-bar">
265
- <div>Reviewed: <span class="stat-value" id="stat-reviewed">0</span></div>
266
- <div>Approved: <span class="stat-value" id="stat-approved">0</span></div>
267
- <div>Rejected: <span class="stat-value" id="stat-rejected">0</span></div>
268
- <div>Approval rate: <span class="stat-value" id="stat-rate">—</span></div>
269
- </div>
270
- </div>
271
-
272
- <div class="progress-container">
273
- <div class="progress-bar">
274
- <div class="progress-fill" id="progress-fill" style="width: 0%"></div>
275
- </div>
276
- <div class="progress-text" id="progress-text">Loading...</div>
277
- </div>
278
-
279
- <div class="main">
280
- <div id="content">
281
- <div class="panels">
282
- <div class="panel">
283
- <div class="panel-header">Input</div>
284
- <div class="panel-body" id="input-display"></div>
285
- </div>
286
- <div class="panel">
287
- <div class="panel-header">Predicted Output</div>
288
- <div class="panel-body" id="output-display"></div>
289
- </div>
290
- </div>
291
-
292
- <div class="actions">
293
- <div class="action-buttons">
294
- <button class="btn btn-approve" id="btn-approve" onclick="score(true)">
295
- 👍 Approve
296
- </button>
297
- <button class="btn btn-reject" id="btn-reject" onclick="score(false)">
298
- 👎 Reject
299
- </button>
300
- </div>
301
-
302
- <div class="correction-area" id="correction-area">
303
- <label for="correction">What should the correct output be?</label>
304
- <textarea id="correction" placeholder="Enter the correct output..."></textarea>
305
- </div>
306
-
307
- <div class="nav">
308
- <button class="nav-btn" id="btn-prev" onclick="navigate(-1)">← Previous</button>
309
- <span class="nav-info" id="nav-info">— / —</span>
310
- <button class="nav-btn" id="btn-next" onclick="navigate(1)">Next →</button>
311
- <button class="done-btn" onclick="finish()">Done</button>
312
- </div>
313
-
314
- <div class="keyboard-hint">
315
- <kbd>→</kbd> Approve &nbsp; <kbd>←</kbd> Reject &nbsp; <kbd>Enter</kbd> Next
316
- </div>
317
- </div>
318
- </div>
319
-
320
- <div class="empty-state" id="empty-state" style="display:none">
321
- <h2>All items reviewed!</h2>
322
- <p>You've reviewed all items. Close this tab or click Done.</p>
323
- </div>
324
- </div>
325
-
326
- <script>
327
- let items = [];
328
- let currentIndex = 0;
329
-
330
- async function init() {
331
- const res = await fetch('/api/items');
332
- items = await res.json();
333
- if (items.length === 0) {
334
- document.getElementById('content').style.display = 'none';
335
- document.getElementById('empty-state').style.display = 'block';
336
- return;
337
- }
338
- // Start at first unreviewed item
339
- const firstUnreviewed = items.findIndex(i => !i.reviewedAt);
340
- currentIndex = firstUnreviewed >= 0 ? firstUnreviewed : 0;
341
- render();
342
- updateStats();
343
- }
344
-
345
- function render() {
346
- const item = items[currentIndex];
347
- if (!item) return;
348
-
349
- document.getElementById('input-display').textContent = item.input;
350
- document.getElementById('output-display').textContent = item.predictedOutput;
351
- document.getElementById('nav-info').textContent =
352
- `${currentIndex + 1} / ${items.length}`;
353
-
354
- document.getElementById('btn-prev').disabled = currentIndex === 0;
355
- document.getElementById('btn-next').disabled = currentIndex === items.length - 1;
356
-
357
- // Show current state
358
- const approveBtn = document.getElementById('btn-approve');
359
- const rejectBtn = document.getElementById('btn-reject');
360
- const correctionArea = document.getElementById('correction-area');
361
-
362
- approveBtn.classList.toggle('active', item.reviewedAt && item.approved === true);
363
- rejectBtn.classList.toggle('active', item.reviewedAt && item.approved === false);
364
-
365
- if (item.reviewedAt && item.approved === false) {
366
- correctionArea.classList.add('visible');
367
- document.getElementById('correction').value = item.correctedOutput || '';
368
- } else {
369
- correctionArea.classList.remove('visible');
370
- document.getElementById('correction').value = '';
371
- }
372
- }
373
-
374
- async function score(approved) {
375
- const item = items[currentIndex];
376
- const correctedOutput = !approved
377
- ? document.getElementById('correction').value.trim() || undefined
378
- : undefined;
379
-
380
- await fetch('/api/score', {
381
- method: 'POST',
382
- headers: { 'Content-Type': 'application/json' },
383
- body: JSON.stringify({
384
- id: item.id,
385
- approved,
386
- correctedOutput,
387
- }),
388
- });
389
-
390
- item.approved = approved;
391
- item.reviewedAt = new Date().toISOString();
392
- if (correctedOutput) item.correctedOutput = correctedOutput;
393
-
394
- // Show/hide correction area
395
- const correctionArea = document.getElementById('correction-area');
396
- if (!approved) {
397
- correctionArea.classList.add('visible');
398
- document.getElementById('correction').focus();
399
- } else {
400
- correctionArea.classList.remove('visible');
401
- }
402
-
403
- render();
404
- updateStats();
405
-
406
- // Auto-advance on approve
407
- if (approved && currentIndex < items.length - 1) {
408
- setTimeout(() => navigate(1), 300);
409
- }
410
- }
411
-
412
- function navigate(delta) {
413
- const newIndex = currentIndex + delta;
414
- if (newIndex >= 0 && newIndex < items.length) {
415
- // Save any pending correction before navigating
416
- const item = items[currentIndex];
417
- if (item.reviewedAt && item.approved === false) {
418
- const correction = document.getElementById('correction').value.trim();
419
- if (correction && correction !== item.correctedOutput) {
420
- fetch('/api/score', {
421
- method: 'POST',
422
- headers: { 'Content-Type': 'application/json' },
423
- body: JSON.stringify({
424
- id: item.id,
425
- approved: false,
426
- correctedOutput: correction,
427
- }),
428
- });
429
- item.correctedOutput = correction;
430
- }
431
- }
432
- currentIndex = newIndex;
433
- render();
434
- }
435
- }
436
-
437
- async function updateStats() {
438
- const res = await fetch('/api/stats');
439
- const stats = await res.json();
440
-
441
- document.getElementById('stat-reviewed').textContent = stats.reviewed;
442
- document.getElementById('stat-approved').textContent = stats.approved;
443
- document.getElementById('stat-rejected').textContent = stats.rejected;
444
- document.getElementById('stat-rate').textContent =
445
- stats.reviewed > 0 ? stats.approvalRate + '%' : '—';
446
-
447
- const pct = stats.total > 0 ? (stats.reviewed / stats.total) * 100 : 0;
448
- document.getElementById('progress-fill').style.width = pct + '%';
449
- document.getElementById('progress-text').textContent =
450
- `${stats.reviewed} of ${stats.total} reviewed`;
451
-
452
- if (stats.remaining === 0 && stats.total > 0) {
453
- document.getElementById('content').style.display = 'none';
454
- document.getElementById('empty-state').style.display = 'block';
455
- }
456
- }
457
-
458
- async function finish() {
459
- if (confirm('End review session?')) {
460
- await fetch('/api/shutdown', { method: 'POST' });
461
- document.body.innerHTML =
462
- '<div style="display:flex;align-items:center;justify-content:center;height:100vh;color:#666;font-family:sans-serif">' +
463
- '<div style="text-align:center"><h2>Review complete</h2><p>You can close this tab.</p></div></div>';
464
- }
465
- }
466
-
467
- // Keyboard shortcuts
468
- document.addEventListener('keydown', (e) => {
469
- if (e.target.tagName === 'TEXTAREA') return;
470
-
471
- if (e.key === 'ArrowRight') {
472
- e.preventDefault();
473
- score(true);
474
- } else if (e.key === 'ArrowLeft') {
475
- e.preventDefault();
476
- score(false);
477
- } else if (e.key === 'Enter') {
478
- e.preventDefault();
479
- navigate(1);
480
- }
481
- });
482
-
483
- init();
484
- </script>
485
- </body>
486
- </html>