@fasaled/sailkari 0.1.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 +229 -0
- package/dist/application.d.ts +47 -0
- package/dist/application.js +100 -0
- package/dist/application.js.map +1 -0
- package/dist/autocomplete.d.ts +2 -0
- package/dist/autocomplete.js +62 -0
- package/dist/autocomplete.js.map +1 -0
- package/dist/classification-store.d.ts +20 -0
- package/dist/classification-store.js +83 -0
- package/dist/classification-store.js.map +1 -0
- package/dist/classifier.d.ts +6 -0
- package/dist/classifier.js +85 -0
- package/dist/classifier.js.map +1 -0
- package/dist/command-history.d.ts +12 -0
- package/dist/command-history.js +49 -0
- package/dist/command-history.js.map +1 -0
- package/dist/command-parser.d.ts +32 -0
- package/dist/command-parser.js +90 -0
- package/dist/command-parser.js.map +1 -0
- package/dist/command-queue.d.ts +21 -0
- package/dist/command-queue.js +33 -0
- package/dist/command-queue.js.map +1 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +26 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +16 -0
- package/dist/context.js +12 -0
- package/dist/context.js.map +1 -0
- package/dist/evaluation-metrics.d.ts +22 -0
- package/dist/evaluation-metrics.js +120 -0
- package/dist/evaluation-metrics.js.map +1 -0
- package/dist/file-scanner.d.ts +2 -0
- package/dist/file-scanner.js +37 -0
- package/dist/file-scanner.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/llm-engine.d.ts +42 -0
- package/dist/llm-engine.js +181 -0
- package/dist/llm-engine.js.map +1 -0
- package/dist/logger.d.ts +15 -0
- package/dist/logger.js +48 -0
- package/dist/logger.js.map +1 -0
- package/dist/mcp.d.ts +8 -0
- package/dist/mcp.js +97 -0
- package/dist/mcp.js.map +1 -0
- package/dist/metadata.d.ts +4 -0
- package/dist/metadata.js +28 -0
- package/dist/metadata.js.map +1 -0
- package/dist/operation-worker.d.ts +1 -0
- package/dist/operation-worker.js +86 -0
- package/dist/operation-worker.js.map +1 -0
- package/dist/parser.d.ts +3 -0
- package/dist/parser.js +86 -0
- package/dist/parser.js.map +1 -0
- package/dist/progress.d.ts +4 -0
- package/dist/progress.js +56 -0
- package/dist/progress.js.map +1 -0
- package/dist/prompt.d.ts +19 -0
- package/dist/prompt.js +63 -0
- package/dist/prompt.js.map +1 -0
- package/dist/tui.d.ts +2 -0
- package/dist/tui.js +237 -0
- package/dist/tui.js.map +1 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/watcher.d.ts +9 -0
- package/dist/watcher.js +94 -0
- package/dist/watcher.js.map +1 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Francisco Sanchez
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# Sailkari
|
|
2
|
+
|
|
3
|
+
**Sailkari** (Basque for “the one who classifies”) is a local benchmark TUI for comparing
|
|
4
|
+
instruction-tuned GGUF models on document classification. Give it a corpus of text files and
|
|
5
|
+
a YAML taxonomy, then run the same workload across models to compare classification output,
|
|
6
|
+
latency, throughput, chunking behavior, and context reuse.
|
|
7
|
+
|
|
8
|
+
Inference runs through [`node-llama-cpp`](https://github.com/withcatai/node-llama-cpp). The
|
|
9
|
+
model, corpus, prompts, and results remain on the local machine. Sailkari is intended for
|
|
10
|
+
evaluating model behavior and performance, not as a production classification service.
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
- Node.js 20 or newer
|
|
15
|
+
- An instruction-tuned GGUF model downloaded separately
|
|
16
|
+
|
|
17
|
+
The npm package includes the compatible llama.cpp native addon and libraries for the current
|
|
18
|
+
platform. npm picks the matching prebuilt package for your OS and architecture, so no
|
|
19
|
+
compiler setup is required for supported platforms.
|
|
20
|
+
|
|
21
|
+
Sailkari works anywhere the native addon runs. It stores classifications in
|
|
22
|
+
`.sailkari/results.json` inside the evaluated folder, making repeated runs portable across
|
|
23
|
+
macOS, Linux, and Windows.
|
|
24
|
+
|
|
25
|
+
## Install and run
|
|
26
|
+
|
|
27
|
+
Run the full-screen terminal interface without a global installation:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx sailkari
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or install globally:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install --global sailkari
|
|
37
|
+
sailkari help
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Sailkari has two presentation modes in the same executable:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Interactive terminal UI
|
|
44
|
+
sailkari
|
|
45
|
+
|
|
46
|
+
# MCP server over stdio for an external agent
|
|
47
|
+
sailkari --mcp
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Both modes use the same application engine, model lifecycle, prompt configuration,
|
|
51
|
+
classification pipeline, context-reuse modes, result store, and benchmark metrics. MCP only
|
|
52
|
+
changes how those operations are exposed to the client. It provides tools for loading a model,
|
|
53
|
+
setting a prompt, evaluating documents, and reading or removing stored classifications.
|
|
54
|
+
|
|
55
|
+
## Architecture
|
|
56
|
+
|
|
57
|
+
Sailkari is a single executable with two presentation layers over the same core:
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
sailkari → TUI (Ink)
|
|
61
|
+
sailkari --mcp → MCP server over stdio
|
|
62
|
+
|
|
63
|
+
shared application core
|
|
64
|
+
├─ model lifecycle
|
|
65
|
+
├─ system prompt management
|
|
66
|
+
├─ document classification
|
|
67
|
+
├─ context reuse modes
|
|
68
|
+
├─ results storage
|
|
69
|
+
└─ evaluation metrics
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The TUI and MCP adapters both call the same application service; they only differ in how they
|
|
73
|
+
render or return the result. This keeps the model logic, evaluation logic, and stored results
|
|
74
|
+
consistent regardless of the client interface.
|
|
75
|
+
|
|
76
|
+
The MCP resource `sailkari://system-prompt-contract` describes the system-prompt format, and
|
|
77
|
+
the `generate-system-prompt` MCP prompt helps an agent produce a compatible prompt.
|
|
78
|
+
|
|
79
|
+
Install from source:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
git clone https://github.com/fasaled/local-ai-classifier.git
|
|
83
|
+
cd local-ai-classifier
|
|
84
|
+
npm install
|
|
85
|
+
npm run build
|
|
86
|
+
node dist/index.js
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Run an evaluation
|
|
90
|
+
|
|
91
|
+
Load a model and evaluate the included example corpus:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
model models/Ministral-3-3B-Instruct-2512-Q4_K_M.gguf
|
|
95
|
+
classify examples/documents examples/labels.yaml --force
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Use `--force` when repeating a benchmark to run inference again for files that already have a
|
|
99
|
+
stored result. Without it, Sailkari displays cached labels and excludes those files from
|
|
100
|
+
inference metrics.
|
|
101
|
+
|
|
102
|
+
## Metrics
|
|
103
|
+
|
|
104
|
+
For every evaluated document, Sailkari shows the predicted label, total duration, inference
|
|
105
|
+
duration, source size, estimated input tokens, model calls, and estimated input throughput.
|
|
106
|
+
The final summary groups results into:
|
|
107
|
+
|
|
108
|
+
- file outcomes: labelled, no match, and cached;
|
|
109
|
+
- timing: setup, total inference, and mean inference time per evaluated file;
|
|
110
|
+
- workload: bytes, estimated input tokens, calls, chunks, and input tokens per second.
|
|
111
|
+
|
|
112
|
+
This keeps quality signals and performance signals together while comparing models.
|
|
113
|
+
|
|
114
|
+
## Context modes
|
|
115
|
+
|
|
116
|
+
The default mode creates a fresh inference context for every model call, maximizing isolation.
|
|
117
|
+
Two optional modes measure the effect of reusing allocated context memory without sharing
|
|
118
|
+
inference history:
|
|
119
|
+
|
|
120
|
+
- `--reuse-context-file`: reuse a context between chunks of the same file, then dispose it.
|
|
121
|
+
- `--reuse-context-command`: reuse one context across the full `classify` command.
|
|
122
|
+
|
|
123
|
+
In both modes, Sailkari clears context history between chunks and documents before the next
|
|
124
|
+
inference request.
|
|
125
|
+
|
|
126
|
+
## Commands
|
|
127
|
+
|
|
128
|
+
Enter these commands in the lower input panel:
|
|
129
|
+
|
|
130
|
+
```text
|
|
131
|
+
model <path.gguf> Load and persist the inference model
|
|
132
|
+
prompt <path|default> Configure the system prompt
|
|
133
|
+
classify <folder> <labels.yaml> Classify a folder
|
|
134
|
+
classify <folder> <labels.yaml> --force
|
|
135
|
+
classify <folder> <labels.yaml> --reuse-context-file
|
|
136
|
+
classify <folder> <labels.yaml> --reuse-context-command
|
|
137
|
+
list-tags <folder> List stored classifications
|
|
138
|
+
remove-tags <folder> Remove stored classifications
|
|
139
|
+
cancel Cancel the active operation
|
|
140
|
+
queue List pending commands
|
|
141
|
+
queue remove <position> Remove a pending command
|
|
142
|
+
queue move <from> <to> Reorder pending commands
|
|
143
|
+
queue clear Remove all pending commands
|
|
144
|
+
help Show command help
|
|
145
|
+
quit Exit Sailkari
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The upper panel displays benchmark tables, summaries, warnings, and errors. Model and system
|
|
149
|
+
prompt configuration is saved in `~/.config/sailkari/config.json`. Use Tab for command and
|
|
150
|
+
path completion. Operations run in a worker thread, so the TUI remains available while a
|
|
151
|
+
model loads or files are classified. New commands are queued and can be inspected, reordered,
|
|
152
|
+
or removed with `queue`; use `cancel` to stop the active operation.
|
|
153
|
+
|
|
154
|
+
Use Up and Down to browse the persistent command history; it is stored with the rest of the
|
|
155
|
+
configuration in `~/.config/sailkari/config.json`. Use Shift+Up and Shift+Down to scroll the
|
|
156
|
+
activity panel; Page Up and Page Down are also supported.
|
|
157
|
+
|
|
158
|
+
## Labels
|
|
159
|
+
|
|
160
|
+
Labels are a YAML map of names to natural-language descriptions:
|
|
161
|
+
|
|
162
|
+
```yaml
|
|
163
|
+
family: "Family documents including personal letters and school documents"
|
|
164
|
+
banking: "Banking and financial documents including statements and tax returns"
|
|
165
|
+
technology: "Technology documents including architecture and security guides"
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
See `examples/labels.yaml` for a larger example.
|
|
169
|
+
|
|
170
|
+
## System prompts
|
|
171
|
+
|
|
172
|
+
Use `prompt default` to use the built-in classifier prompt. To use a custom prompt, run
|
|
173
|
+
`prompt examples/system-prompt.txt`.
|
|
174
|
+
|
|
175
|
+
The parser accepts:
|
|
176
|
+
|
|
177
|
+
- a bare label name, such as `banking`;
|
|
178
|
+
- JSON such as `{"labels":["banking"]}` or `[{"label":"banking"}]`.
|
|
179
|
+
|
|
180
|
+
The system prompt should request exactly one label and no explanation. Unknown or invalid
|
|
181
|
+
output is treated as no match.
|
|
182
|
+
|
|
183
|
+
## Local execution
|
|
184
|
+
|
|
185
|
+
The model remains loaded for the TUI session and is released when the application exits.
|
|
186
|
+
Contexts follow the selected benchmark mode and are always disposed after their scope ends.
|
|
187
|
+
No model or document data is sent over the network; npm may access the network during
|
|
188
|
+
installation to fetch dependencies and the platform-specific native prebuilt.
|
|
189
|
+
|
|
190
|
+
## Packaging
|
|
191
|
+
|
|
192
|
+
The published Sailkari package contains the compiled JavaScript CLI. `node-llama-cpp` is a
|
|
193
|
+
runtime dependency and provides the platform packages for each supported environment, such as
|
|
194
|
+
`@node-llama-cpp/mac-arm64-metal`, `@node-llama-cpp/linux-x64`, and
|
|
195
|
+
`@node-llama-cpp/win-x64`.
|
|
196
|
+
|
|
197
|
+
GGUF files are excluded from the package and are provided separately through the `model`
|
|
198
|
+
command.
|
|
199
|
+
|
|
200
|
+
## Development
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
npm install
|
|
204
|
+
npm run typecheck
|
|
205
|
+
npm test
|
|
206
|
+
npm run build
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Run model-dependent tests explicitly:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
CLASSIFIER_MODEL=/absolute/path/to/model.gguf npm run test:e2e
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Without `CLASSIFIER_MODEL` (and without a GGUF directly in `models/`), the e2e suite is
|
|
216
|
+
skipped.
|
|
217
|
+
|
|
218
|
+
## Contributing
|
|
219
|
+
|
|
220
|
+
Development happens in the [Sailkari repository](https://github.com/fasaled/local-ai-classifier).
|
|
221
|
+
Open an [issue](https://github.com/fasaled/local-ai-classifier/issues) to report a bug, request
|
|
222
|
+
a feature, or discuss a proposed change. Contributions are welcome through pull requests.
|
|
223
|
+
|
|
224
|
+
## Scope
|
|
225
|
+
|
|
226
|
+
- Plain text formats only: `.txt`, `.md`, `.csv`, `.json`, `.yaml`, `.yml`, `.xml`, `.log`,
|
|
227
|
+
`.conf`, `.config`, `.ini`, `.toml`, and `.properties`.
|
|
228
|
+
- Results are stored per evaluated folder in `.sailkari/results.json`.
|
|
229
|
+
- Models are not downloaded automatically.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type NativeLogCallback } from "./llm-engine.js";
|
|
2
|
+
import { summarizeEvaluation } from "./evaluation-metrics.js";
|
|
3
|
+
import type { ProcessingResult } from "./types.js";
|
|
4
|
+
export interface EvaluationOptions {
|
|
5
|
+
folder: string;
|
|
6
|
+
labels: string;
|
|
7
|
+
force: boolean;
|
|
8
|
+
contextReuse: "none" | "file" | "command";
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
onProgress?: (filePath: string, current: number, total: number, message: string) => void;
|
|
11
|
+
onResult?: (result: ProcessingResult) => void;
|
|
12
|
+
onStart?: (folder: string, fileCount: number) => void;
|
|
13
|
+
}
|
|
14
|
+
export interface EvaluationResult {
|
|
15
|
+
folder: string;
|
|
16
|
+
results: ProcessingResult[];
|
|
17
|
+
summary: ReturnType<typeof summarizeEvaluation>;
|
|
18
|
+
}
|
|
19
|
+
export declare class SailkariApplication {
|
|
20
|
+
private readonly nativeLogger?;
|
|
21
|
+
private engine;
|
|
22
|
+
private modelPath;
|
|
23
|
+
private systemPrompt;
|
|
24
|
+
constructor(nativeLogger?: NativeLogCallback | undefined);
|
|
25
|
+
get loadedModelPath(): string | undefined;
|
|
26
|
+
get configuredSystemPrompt(): string;
|
|
27
|
+
loadModel(modelPath: string): Promise<string>;
|
|
28
|
+
setSystemPrompt(prompt: string): Promise<{
|
|
29
|
+
warnings: string[];
|
|
30
|
+
}>;
|
|
31
|
+
loadSystemPrompt(path: string | null): Promise<{
|
|
32
|
+
path: string | null;
|
|
33
|
+
prompt: string;
|
|
34
|
+
warnings: string[];
|
|
35
|
+
}>;
|
|
36
|
+
evaluate(options: EvaluationOptions): Promise<EvaluationResult>;
|
|
37
|
+
listClassifications(folderPath: string): {
|
|
38
|
+
filePath: string;
|
|
39
|
+
labels: string[];
|
|
40
|
+
}[];
|
|
41
|
+
removeClassifications(folderPath: string): {
|
|
42
|
+
filePath: string;
|
|
43
|
+
labels: string[];
|
|
44
|
+
}[];
|
|
45
|
+
dispose(): Promise<void>;
|
|
46
|
+
static fileName(filePath: string): string;
|
|
47
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { basename, resolve } from "node:path";
|
|
2
|
+
import { ClassificationStore } from "./classification-store.js";
|
|
3
|
+
import { loadLabels, processFile } from "./classifier.js";
|
|
4
|
+
import { scanFolder } from "./file-scanner.js";
|
|
5
|
+
import { createLLMEngine } from "./llm-engine.js";
|
|
6
|
+
import { DEFAULT_SYSTEM_PROMPT, inspectSystemPrompt, loadSystemPrompt } from "./prompt.js";
|
|
7
|
+
import { summarizeEvaluation } from "./evaluation-metrics.js";
|
|
8
|
+
export class SailkariApplication {
|
|
9
|
+
nativeLogger;
|
|
10
|
+
engine;
|
|
11
|
+
modelPath;
|
|
12
|
+
systemPrompt = DEFAULT_SYSTEM_PROMPT;
|
|
13
|
+
constructor(nativeLogger) {
|
|
14
|
+
this.nativeLogger = nativeLogger;
|
|
15
|
+
this.engine = createLLMEngine(nativeLogger ?? (() => { }));
|
|
16
|
+
}
|
|
17
|
+
get loadedModelPath() {
|
|
18
|
+
return this.modelPath;
|
|
19
|
+
}
|
|
20
|
+
get configuredSystemPrompt() {
|
|
21
|
+
return this.systemPrompt;
|
|
22
|
+
}
|
|
23
|
+
async loadModel(modelPath) {
|
|
24
|
+
const absolutePath = resolve(modelPath);
|
|
25
|
+
await this.engine.dispose();
|
|
26
|
+
this.engine = createLLMEngine(this.nativeLogger ?? (() => { }));
|
|
27
|
+
await this.engine.loadModel(absolutePath);
|
|
28
|
+
this.modelPath = absolutePath;
|
|
29
|
+
return absolutePath;
|
|
30
|
+
}
|
|
31
|
+
async setSystemPrompt(prompt) {
|
|
32
|
+
this.systemPrompt = prompt.trim();
|
|
33
|
+
return { warnings: inspectSystemPrompt(this.systemPrompt) };
|
|
34
|
+
}
|
|
35
|
+
async loadSystemPrompt(path) {
|
|
36
|
+
if (path === null) {
|
|
37
|
+
this.systemPrompt = DEFAULT_SYSTEM_PROMPT;
|
|
38
|
+
return { path: null, prompt: this.systemPrompt, warnings: [] };
|
|
39
|
+
}
|
|
40
|
+
const absolutePath = resolve(path);
|
|
41
|
+
const prompt = await loadSystemPrompt(absolutePath);
|
|
42
|
+
this.systemPrompt = prompt;
|
|
43
|
+
return { path: absolutePath, prompt, warnings: inspectSystemPrompt(prompt) };
|
|
44
|
+
}
|
|
45
|
+
async evaluate(options) {
|
|
46
|
+
const startedAt = performance.now();
|
|
47
|
+
const labels = await loadLabels(resolve(options.labels));
|
|
48
|
+
const folder = resolve(options.folder);
|
|
49
|
+
const files = scanFolder(folder);
|
|
50
|
+
const preparationMs = performance.now() - startedAt;
|
|
51
|
+
options.onStart?.(folder, files.length);
|
|
52
|
+
const store = new ClassificationStore(folder);
|
|
53
|
+
const results = [];
|
|
54
|
+
const commandContext = options.contextReuse === "command" ? await this.engine.createContext() : undefined;
|
|
55
|
+
try {
|
|
56
|
+
for (const filePath of files) {
|
|
57
|
+
options.signal?.throwIfAborted();
|
|
58
|
+
if (commandContext && results.length > 0)
|
|
59
|
+
await commandContext.clearHistory();
|
|
60
|
+
const fileContext = options.contextReuse === "file"
|
|
61
|
+
? await this.engine.createContext()
|
|
62
|
+
: commandContext;
|
|
63
|
+
try {
|
|
64
|
+
const result = await processFile(filePath, labels, options.force, this.engine, store, this.systemPrompt, options.signal, options.onProgress ? (current, total, message) => options.onProgress(filePath, current, total, message) : undefined, fileContext);
|
|
65
|
+
results.push(result);
|
|
66
|
+
options.onResult?.(result);
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
if (options.contextReuse === "file")
|
|
70
|
+
await fileContext?.dispose();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
await commandContext?.dispose();
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
folder,
|
|
79
|
+
results,
|
|
80
|
+
summary: summarizeEvaluation(results, performance.now() - startedAt, preparationMs),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
listClassifications(folderPath) {
|
|
84
|
+
return new ClassificationStore(resolve(folderPath)).entries();
|
|
85
|
+
}
|
|
86
|
+
removeClassifications(folderPath) {
|
|
87
|
+
const store = new ClassificationStore(resolve(folderPath));
|
|
88
|
+
const entries = store.entries();
|
|
89
|
+
for (const entry of entries)
|
|
90
|
+
store.remove(entry.filePath);
|
|
91
|
+
return entries;
|
|
92
|
+
}
|
|
93
|
+
async dispose() {
|
|
94
|
+
await this.engine.dispose();
|
|
95
|
+
}
|
|
96
|
+
static fileName(filePath) {
|
|
97
|
+
return basename(filePath);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=application.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"application.js","sourceRoot":"","sources":["../src/application.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
3
|
+
const COMMANDS = ["model", "prompt", "classify", "list-tags", "remove-tags", "help", "quit"];
|
|
4
|
+
function currentToken(input) {
|
|
5
|
+
const match = input.match(/(?:^|\s)([^\s]*)$/);
|
|
6
|
+
if (!match)
|
|
7
|
+
return { before: input, token: "" };
|
|
8
|
+
return { before: input.slice(0, input.length - match[1].length), token: match[1] };
|
|
9
|
+
}
|
|
10
|
+
function pathCandidates(token, predicate, defaultBase = ".") {
|
|
11
|
+
const hasTrailingSeparator = token.endsWith("/");
|
|
12
|
+
const base = !token ? defaultBase : hasTrailingSeparator ? token : token.includes("/") ? dirname(token) : defaultBase;
|
|
13
|
+
const prefix = token && !hasTrailingSeparator ? basename(token) : "";
|
|
14
|
+
const directory = resolve(base === "." && token.startsWith("/") ? "/" : base);
|
|
15
|
+
try {
|
|
16
|
+
return readdirSync(directory, { withFileTypes: true })
|
|
17
|
+
.filter((entry) => entry.name.startsWith(prefix))
|
|
18
|
+
.map((entry) => join(base, entry.name))
|
|
19
|
+
.filter((candidate) => {
|
|
20
|
+
try {
|
|
21
|
+
return statSync(resolve(candidate)).isDirectory() || predicate(resolve(candidate));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
.map((candidate) => candidate + (statSync(resolve(candidate)).isDirectory() ? "/" : ""));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function completeInput(input) {
|
|
34
|
+
const { before, token } = currentToken(input);
|
|
35
|
+
if (!before && !token.includes("/")) {
|
|
36
|
+
return COMMANDS.filter((command) => command.startsWith(token));
|
|
37
|
+
}
|
|
38
|
+
const command = input.trim().split(/\s+/)[0];
|
|
39
|
+
const tokens = input.trim().split(/\s+/);
|
|
40
|
+
const argumentIndex = tokens.length - 1 + (input.endsWith(" ") ? 1 : 0);
|
|
41
|
+
if (command === "model" && argumentIndex === 1) {
|
|
42
|
+
return pathCandidates(token, (path) => extname(path).toLowerCase() === ".gguf");
|
|
43
|
+
}
|
|
44
|
+
if (command === "prompt" && argumentIndex === 1) {
|
|
45
|
+
return pathCandidates(token, (path) => statSync(path).isFile());
|
|
46
|
+
}
|
|
47
|
+
if (command === "classify" && argumentIndex === 1) {
|
|
48
|
+
return pathCandidates(token, (path) => statSync(path).isDirectory());
|
|
49
|
+
}
|
|
50
|
+
if (command === "classify" && argumentIndex === 2) {
|
|
51
|
+
return pathCandidates(token, (path) => [".yaml", ".yml"].includes(extname(path).toLowerCase()));
|
|
52
|
+
}
|
|
53
|
+
if ((command === "list-tags" || command === "remove-tags") && argumentIndex === 1) {
|
|
54
|
+
return pathCandidates(token, (path) => statSync(path).isDirectory());
|
|
55
|
+
}
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
export function applyCompletion(input, completion) {
|
|
59
|
+
const { before } = currentToken(input);
|
|
60
|
+
return before + completion;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=autocomplete.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autocomplete.js","sourceRoot":"","sources":["../src/autocomplete.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare class ClassificationStore {
|
|
2
|
+
private readonly root;
|
|
3
|
+
private readonly filePath;
|
|
4
|
+
private results;
|
|
5
|
+
private loaded;
|
|
6
|
+
constructor(folderPath: string);
|
|
7
|
+
has(filePath: string): boolean;
|
|
8
|
+
getLabels(filePath: string): string[];
|
|
9
|
+
getTags(filePath: string): string[];
|
|
10
|
+
setLabels(filePath: string, labels: string[]): void;
|
|
11
|
+
remove(filePath: string): void;
|
|
12
|
+
entries(): {
|
|
13
|
+
filePath: string;
|
|
14
|
+
labels: string[];
|
|
15
|
+
}[];
|
|
16
|
+
private keyFor;
|
|
17
|
+
private load;
|
|
18
|
+
private save;
|
|
19
|
+
}
|
|
20
|
+
export declare function isClassificationStorePath(filePath: string): boolean;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
const STORE_DIRECTORY = ".sailkari";
|
|
4
|
+
const STORE_FILE = "results.json";
|
|
5
|
+
const STORE_VERSION = 1;
|
|
6
|
+
function emptyResults() {
|
|
7
|
+
return { version: STORE_VERSION, files: {} };
|
|
8
|
+
}
|
|
9
|
+
export class ClassificationStore {
|
|
10
|
+
root;
|
|
11
|
+
filePath;
|
|
12
|
+
results;
|
|
13
|
+
loaded = false;
|
|
14
|
+
constructor(folderPath) {
|
|
15
|
+
this.root = resolve(folderPath);
|
|
16
|
+
this.filePath = join(this.root, STORE_DIRECTORY, STORE_FILE);
|
|
17
|
+
this.results = emptyResults();
|
|
18
|
+
}
|
|
19
|
+
has(filePath) {
|
|
20
|
+
this.load();
|
|
21
|
+
return this.keyFor(filePath) in this.results.files;
|
|
22
|
+
}
|
|
23
|
+
getLabels(filePath) {
|
|
24
|
+
this.load();
|
|
25
|
+
return [...(this.results.files[this.keyFor(filePath)]?.labels ?? [])];
|
|
26
|
+
}
|
|
27
|
+
getTags(filePath) {
|
|
28
|
+
const labels = this.getLabels(filePath);
|
|
29
|
+
if (this.has(filePath)) {
|
|
30
|
+
return [...labels, "ai-classified"];
|
|
31
|
+
}
|
|
32
|
+
return labels;
|
|
33
|
+
}
|
|
34
|
+
setLabels(filePath, labels) {
|
|
35
|
+
this.load();
|
|
36
|
+
this.results.files[this.keyFor(filePath)] = { labels: [...labels] };
|
|
37
|
+
this.save();
|
|
38
|
+
}
|
|
39
|
+
remove(filePath) {
|
|
40
|
+
this.load();
|
|
41
|
+
delete this.results.files[this.keyFor(filePath)];
|
|
42
|
+
this.save();
|
|
43
|
+
}
|
|
44
|
+
entries() {
|
|
45
|
+
this.load();
|
|
46
|
+
return Object.entries(this.results.files).map(([key, value]) => ({
|
|
47
|
+
filePath: join(this.root, key.split("/").join(sep)),
|
|
48
|
+
labels: [...value.labels],
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
keyFor(filePath) {
|
|
52
|
+
return relative(this.root, resolve(filePath)).split(sep).join("/");
|
|
53
|
+
}
|
|
54
|
+
load() {
|
|
55
|
+
if (this.loaded)
|
|
56
|
+
return;
|
|
57
|
+
this.loaded = true;
|
|
58
|
+
try {
|
|
59
|
+
const parsed = JSON.parse(readFileSync(this.filePath, "utf8"));
|
|
60
|
+
if (parsed.version === STORE_VERSION && parsed.files && typeof parsed.files === "object") {
|
|
61
|
+
this.results = {
|
|
62
|
+
version: STORE_VERSION,
|
|
63
|
+
files: Object.fromEntries(Object.entries(parsed.files).filter(([, value]) => {
|
|
64
|
+
return value && Array.isArray(value.labels);
|
|
65
|
+
})),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
this.results = emptyResults();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
save() {
|
|
74
|
+
mkdirSync(dirname(this.filePath), { recursive: true });
|
|
75
|
+
const temporaryPath = `${this.filePath}.tmp-${process.pid}`;
|
|
76
|
+
writeFileSync(temporaryPath, JSON.stringify(this.results, null, 2) + "\n", "utf8");
|
|
77
|
+
renameSync(temporaryPath, this.filePath);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export function isClassificationStorePath(filePath) {
|
|
81
|
+
return filePath.split(sep).includes(STORE_DIRECTORY);
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=classification-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classification-store.js","sourceRoot":"","sources":["../src/classification-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAElE,MAAM,eAAe,GAAG,WAAW,CAAC;AACpC,MAAM,UAAU,GAAG,cAAc,CAAC;AAClC,MAAM,aAAa,GAAG,CAAC,CAAC;AAWxB,SAAS,YAAY;IACnB,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,OAAO,mBAAmB;IACb,IAAI,CAAS;IACb,QAAQ,CAAS;IAC1B,OAAO,CAAoB;IAC3B,MAAM,GAAG,KAAK,CAAC;IAEvB,YAAY,UAAkB;QAC5B,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;QAChC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,eAAe,EAAE,UAAU,CAAC,CAAC;QAC7D,IAAI,CAAC,OAAO,GAAG,YAAY,EAAE,CAAC;IAChC,CAAC;IAED,GAAG,CAAC,QAAgB;QAClB,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;IACrD,CAAC;IAED,SAAS,CAAC,QAAgB;QACxB,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC;IACxE,CAAC;IAED,OAAO,CAAC,QAAgB;QACtB,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACxC,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YACvB,OAAO,CAAC,GAAG,MAAM,EAAE,eAAe,CAAC,CAAC;QACtC,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,SAAS,CAAC,QAAgB,EAAE,MAAgB;QAC1C,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC,EAAE,CAAC;QACpE,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IAED,MAAM,CAAC,QAAgB;QACrB,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QACjD,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IAED,OAAO;QACL,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;YAC/D,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACnD,MAAM,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;SAC1B,CAAC,CAAC,CAAC;IACN,CAAC;IAEO,MAAM,CAAC,QAAgB;QAC7B,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrE,CAAC;IAEO,IAAI;QACV,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QAEnB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAA+B,CAAC;YAC7F,IAAI,MAAM,CAAC,OAAO,KAAK,aAAa,IAAI,MAAM,CAAC,KAAK,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;gBACzF,IAAI,CAAC,OAAO,GAAG;oBACb,OAAO,EAAE,aAAa;oBACtB,KAAK,EAAE,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE;wBAChD,OAAO,KAAK,IAAI,KAAK,CAAC,OAAO,CAAE,KAAsB,CAAC,MAAM,CAAC,CAAC;oBAChE,CAAC,CAAC,CAC6B;iBAClC,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,OAAO,GAAG,YAAY,EAAE,CAAC;QAChC,CAAC;IACH,CAAC;IAEO,IAAI;QACV,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,MAAM,aAAa,GAAG,GAAG,IAAI,CAAC,QAAQ,QAAQ,OAAO,CAAC,GAAG,EAAE,CAAC;QAC5D,aAAa,CAAC,aAAa,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;QACnF,UAAU,CAAC,aAAa,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC3C,CAAC;CACF;AAED,MAAM,UAAU,yBAAyB,CAAC,QAAgB;IACxD,OAAO,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;AACvD,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { EngineContext, LLMEngine } from "./llm-engine.js";
|
|
2
|
+
import type { Label, ProcessingResult } from "./types.js";
|
|
3
|
+
import type { ClassificationStore } from "./classification-store.js";
|
|
4
|
+
import type { ProgressCallback } from "./llm-engine.js";
|
|
5
|
+
export declare function processFile(filePath: string, labels: Label[], force: boolean, engine: LLMEngine, store: ClassificationStore, systemPrompt?: string, signal?: AbortSignal, onProgress?: ProgressCallback, context?: EngineContext): Promise<ProcessingResult>;
|
|
6
|
+
export declare function loadLabels(labelsPath: string): Promise<Label[]>;
|