llm-checker 3.2.0 → 3.2.2

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.
@@ -0,0 +1,324 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const YAML = require('yaml');
4
+
5
+ const ALLOWED_POLICY_MODES = ['audit', 'enforce'];
6
+ const ALLOWED_ENFORCEMENT_BEHAVIOR = ['warn', 'error'];
7
+ const ALLOWED_REPORT_FORMATS = ['json', 'csv', 'sarif'];
8
+
9
+ function isPlainObject(value) {
10
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
11
+ }
12
+
13
+ function isPositiveNumber(value) {
14
+ return typeof value === 'number' && Number.isFinite(value) && value > 0;
15
+ }
16
+
17
+ function isNonEmptyString(value) {
18
+ return typeof value === 'string' && value.trim().length > 0;
19
+ }
20
+
21
+ class PolicyManager {
22
+ getTemplate() {
23
+ return `version: 1
24
+ org: your-org
25
+ mode: enforce # audit | enforce
26
+
27
+ rules:
28
+ models:
29
+ allow:
30
+ - "qwen2.5-coder:*"
31
+ - "llama3.1:*"
32
+ deny:
33
+ - "*uncensored*"
34
+ max_size_gb: 24
35
+ max_params_b: 32
36
+ allowed_quantizations: ["Q4_K_M", "Q5_K_M", "Q8_0"]
37
+
38
+ runtime:
39
+ required_backends: ["metal", "cuda"]
40
+ min_ram_gb: 32
41
+ local_only: true
42
+
43
+ compliance:
44
+ approved_licenses: ["mit", "apache-2.0", "llama"]
45
+
46
+ enforcement:
47
+ on_violation: error # warn | error
48
+ exit_code: 3
49
+ allow_exceptions: true
50
+
51
+ exceptions:
52
+ - model: "deepseek-r1:32b"
53
+ reason: "Approved PoC"
54
+ approver: "security@example.com"
55
+ expires_at: "2026-06-30"
56
+
57
+ reporting:
58
+ formats: ["json", "csv", "sarif"]
59
+ `;
60
+ }
61
+
62
+ resolvePolicyPath(policyFile = 'policy.yaml', cwd = process.cwd()) {
63
+ return path.isAbsolute(policyFile) ? policyFile : path.resolve(cwd, policyFile);
64
+ }
65
+
66
+ initPolicy(policyFile = 'policy.yaml', options = {}) {
67
+ const { force = false, cwd = process.cwd() } = options;
68
+ const targetPath = this.resolvePolicyPath(policyFile, cwd);
69
+
70
+ const exists = fs.existsSync(targetPath);
71
+ if (exists && !force) {
72
+ throw new Error(`Policy file already exists at ${targetPath}. Use --force to overwrite.`);
73
+ }
74
+
75
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
76
+ fs.writeFileSync(targetPath, this.getTemplate(), 'utf8');
77
+
78
+ return {
79
+ path: targetPath,
80
+ overwritten: exists && force
81
+ };
82
+ }
83
+
84
+ loadPolicy(policyFile = 'policy.yaml', options = {}) {
85
+ const { cwd = process.cwd() } = options;
86
+ const policyPath = this.resolvePolicyPath(policyFile, cwd);
87
+
88
+ if (!fs.existsSync(policyPath)) {
89
+ throw new Error(`Policy file not found: ${policyPath}`);
90
+ }
91
+
92
+ const source = fs.readFileSync(policyPath, 'utf8');
93
+ const doc = YAML.parseDocument(source, { prettyErrors: true });
94
+
95
+ if (doc.errors && doc.errors.length > 0) {
96
+ const first = doc.errors[0];
97
+ throw new Error(`Invalid YAML in ${policyPath}: ${String(first.message || first)}`);
98
+ }
99
+
100
+ const policy = doc.toJSON();
101
+ if (!isPlainObject(policy)) {
102
+ throw new Error(`Invalid YAML in ${policyPath}: root must be an object`);
103
+ }
104
+
105
+ return { path: policyPath, policy };
106
+ }
107
+
108
+ validatePolicyFile(policyFile = 'policy.yaml', options = {}) {
109
+ const loaded = this.loadPolicy(policyFile, options);
110
+ const validation = this.validatePolicyObject(loaded.policy);
111
+ return {
112
+ ...validation,
113
+ path: loaded.path,
114
+ policy: loaded.policy
115
+ };
116
+ }
117
+
118
+ validatePolicyObject(policy) {
119
+ const errors = [];
120
+ const addError = (fieldPath, message) => {
121
+ errors.push({ path: fieldPath, message });
122
+ };
123
+
124
+ if (!isPlainObject(policy)) {
125
+ addError('root', 'Policy must be an object.');
126
+ return { valid: false, errors };
127
+ }
128
+
129
+ if (!Number.isInteger(policy.version) || policy.version < 1) {
130
+ addError('version', 'Version must be an integer >= 1.');
131
+ }
132
+
133
+ if (!isNonEmptyString(policy.org)) {
134
+ addError('org', 'Organization must be a non-empty string.');
135
+ }
136
+
137
+ if (!isNonEmptyString(policy.mode) || !ALLOWED_POLICY_MODES.includes(policy.mode)) {
138
+ addError('mode', `Mode must be one of: ${ALLOWED_POLICY_MODES.join(', ')}.`);
139
+ }
140
+
141
+ if (!isPlainObject(policy.rules)) {
142
+ addError('rules', 'Rules section is required and must be an object.');
143
+ } else {
144
+ this.validateModelsRules(policy.rules.models, addError);
145
+ this.validateRuntimeRules(policy.rules.runtime, addError);
146
+ this.validateComplianceRules(policy.rules.compliance, addError);
147
+ }
148
+
149
+ this.validateEnforcement(policy.enforcement, addError);
150
+ this.validateExceptions(policy.exceptions, addError);
151
+ this.validateReporting(policy.reporting, addError);
152
+
153
+ return {
154
+ valid: errors.length === 0,
155
+ errors
156
+ };
157
+ }
158
+
159
+ validateModelsRules(models, addError) {
160
+ if (models === undefined) return;
161
+
162
+ if (!isPlainObject(models)) {
163
+ addError('rules.models', 'Must be an object.');
164
+ return;
165
+ }
166
+
167
+ this.validateStringArray(models.allow, 'rules.models.allow', addError);
168
+ this.validateStringArray(models.deny, 'rules.models.deny', addError);
169
+ this.validateStringArray(
170
+ models.allowed_quantizations,
171
+ 'rules.models.allowed_quantizations',
172
+ addError
173
+ );
174
+
175
+ if (models.max_size_gb !== undefined && !isPositiveNumber(models.max_size_gb)) {
176
+ addError('rules.models.max_size_gb', 'Must be a positive number.');
177
+ }
178
+
179
+ if (models.max_params_b !== undefined && !isPositiveNumber(models.max_params_b)) {
180
+ addError('rules.models.max_params_b', 'Must be a positive number.');
181
+ }
182
+ }
183
+
184
+ validateRuntimeRules(runtime, addError) {
185
+ if (runtime === undefined) return;
186
+
187
+ if (!isPlainObject(runtime)) {
188
+ addError('rules.runtime', 'Must be an object.');
189
+ return;
190
+ }
191
+
192
+ this.validateStringArray(runtime.required_backends, 'rules.runtime.required_backends', addError);
193
+
194
+ if (runtime.min_ram_gb !== undefined && !isPositiveNumber(runtime.min_ram_gb)) {
195
+ addError('rules.runtime.min_ram_gb', 'Must be a positive number.');
196
+ }
197
+
198
+ if (runtime.local_only !== undefined && typeof runtime.local_only !== 'boolean') {
199
+ addError('rules.runtime.local_only', 'Must be a boolean.');
200
+ }
201
+ }
202
+
203
+ validateComplianceRules(compliance, addError) {
204
+ if (compliance === undefined) return;
205
+
206
+ if (!isPlainObject(compliance)) {
207
+ addError('rules.compliance', 'Must be an object.');
208
+ return;
209
+ }
210
+
211
+ this.validateStringArray(
212
+ compliance.approved_licenses,
213
+ 'rules.compliance.approved_licenses',
214
+ addError
215
+ );
216
+ }
217
+
218
+ validateEnforcement(enforcement, addError) {
219
+ if (enforcement === undefined) return;
220
+
221
+ if (!isPlainObject(enforcement)) {
222
+ addError('enforcement', 'Must be an object.');
223
+ return;
224
+ }
225
+
226
+ if (
227
+ enforcement.on_violation !== undefined &&
228
+ !ALLOWED_ENFORCEMENT_BEHAVIOR.includes(enforcement.on_violation)
229
+ ) {
230
+ addError(
231
+ 'enforcement.on_violation',
232
+ `Must be one of: ${ALLOWED_ENFORCEMENT_BEHAVIOR.join(', ')}.`
233
+ );
234
+ }
235
+
236
+ if (enforcement.exit_code !== undefined) {
237
+ if (!Number.isInteger(enforcement.exit_code) || enforcement.exit_code < 1 || enforcement.exit_code > 255) {
238
+ addError('enforcement.exit_code', 'Must be an integer between 1 and 255.');
239
+ }
240
+ }
241
+
242
+ if (enforcement.allow_exceptions !== undefined && typeof enforcement.allow_exceptions !== 'boolean') {
243
+ addError('enforcement.allow_exceptions', 'Must be a boolean.');
244
+ }
245
+ }
246
+
247
+ validateExceptions(exceptions, addError) {
248
+ if (exceptions === undefined) return;
249
+
250
+ if (!Array.isArray(exceptions)) {
251
+ addError('exceptions', 'Must be an array.');
252
+ return;
253
+ }
254
+
255
+ exceptions.forEach((entry, index) => {
256
+ const basePath = `exceptions[${index}]`;
257
+ if (!isPlainObject(entry)) {
258
+ addError(basePath, 'Each exception must be an object.');
259
+ return;
260
+ }
261
+
262
+ if (!isNonEmptyString(entry.model)) {
263
+ addError(`${basePath}.model`, 'Model must be a non-empty string.');
264
+ }
265
+
266
+ if (entry.reason !== undefined && !isNonEmptyString(entry.reason)) {
267
+ addError(`${basePath}.reason`, 'Reason must be a non-empty string.');
268
+ }
269
+
270
+ if (entry.approver !== undefined && !isNonEmptyString(entry.approver)) {
271
+ addError(`${basePath}.approver`, 'Approver must be a non-empty string.');
272
+ }
273
+
274
+ if (entry.expires_at !== undefined && !isNonEmptyString(entry.expires_at)) {
275
+ addError(`${basePath}.expires_at`, 'expires_at must be a non-empty string.');
276
+ }
277
+ });
278
+ }
279
+
280
+ validateReporting(reporting, addError) {
281
+ if (reporting === undefined) return;
282
+
283
+ if (!isPlainObject(reporting)) {
284
+ addError('reporting', 'Must be an object.');
285
+ return;
286
+ }
287
+
288
+ if (reporting.formats !== undefined) {
289
+ if (!Array.isArray(reporting.formats)) {
290
+ addError('reporting.formats', 'Must be an array.');
291
+ } else {
292
+ reporting.formats.forEach((format, index) => {
293
+ if (!isNonEmptyString(format)) {
294
+ addError(`reporting.formats[${index}]`, 'Format must be a non-empty string.');
295
+ return;
296
+ }
297
+
298
+ if (!ALLOWED_REPORT_FORMATS.includes(format)) {
299
+ addError(
300
+ `reporting.formats[${index}]`,
301
+ `Unsupported format "${format}". Allowed: ${ALLOWED_REPORT_FORMATS.join(', ')}.`
302
+ );
303
+ }
304
+ });
305
+ }
306
+ }
307
+ }
308
+
309
+ validateStringArray(value, fieldPath, addError) {
310
+ if (value === undefined) return;
311
+ if (!Array.isArray(value)) {
312
+ addError(fieldPath, 'Must be an array of strings.');
313
+ return;
314
+ }
315
+
316
+ value.forEach((entry, index) => {
317
+ if (!isNonEmptyString(entry)) {
318
+ addError(`${fieldPath}[${index}]`, 'Must be a non-empty string.');
319
+ }
320
+ });
321
+ }
322
+ }
323
+
324
+ module.exports = PolicyManager;
@@ -0,0 +1,176 @@
1
+ const UNKNOWN_VALUE = 'unknown';
2
+
3
+ const UNKNOWN_MARKERS = new Set(['', 'unknown', 'n/a', 'na', 'none', 'unspecified', 'not-provided']);
4
+
5
+ const LICENSE_ALIASES = {
6
+ mitlicense: 'mit',
7
+ mit: 'mit',
8
+ apache2: 'apache-2.0',
9
+ 'apache2.0': 'apache-2.0',
10
+ 'apache-2': 'apache-2.0',
11
+ 'apache-2.0': 'apache-2.0',
12
+ 'apache-2.0-license': 'apache-2.0',
13
+ 'llama2': 'llama',
14
+ 'llama3': 'llama',
15
+ 'llama3.1': 'llama',
16
+ 'llama3.2': 'llama',
17
+ 'meta-llama': 'llama'
18
+ };
19
+
20
+ function asString(value) {
21
+ if (value === undefined || value === null) return '';
22
+ return String(value).trim();
23
+ }
24
+
25
+ function sanitizeValue(value) {
26
+ const text = asString(value);
27
+ if (!text) return UNKNOWN_VALUE;
28
+
29
+ const normalized = text.toLowerCase();
30
+ return UNKNOWN_MARKERS.has(normalized) ? UNKNOWN_VALUE : text;
31
+ }
32
+
33
+ function normalizeSource(value) {
34
+ const normalized = sanitizeValue(value).toLowerCase();
35
+ if (normalized === UNKNOWN_VALUE) return UNKNOWN_VALUE;
36
+
37
+ const source = normalized.replace(/\s+/g, '_').replace(/-/g, '_');
38
+
39
+ if (source === 'ollama_local') return 'ollama_local';
40
+ if (source === 'ollama_database') return 'ollama_database';
41
+ if (source === 'enhanced_with_ollama') return 'enhanced_with_ollama';
42
+ if (source === 'static_database') return 'static_database';
43
+
44
+ return source;
45
+ }
46
+
47
+ function normalizeRegistry(value, source = UNKNOWN_VALUE) {
48
+ const registry = sanitizeValue(value);
49
+ if (registry !== UNKNOWN_VALUE) return registry;
50
+
51
+ if (source.includes('ollama')) {
52
+ return 'ollama.com';
53
+ }
54
+
55
+ return UNKNOWN_VALUE;
56
+ }
57
+
58
+ function normalizeVersion(value) {
59
+ return sanitizeValue(value);
60
+ }
61
+
62
+ function normalizeDigest(value) {
63
+ return sanitizeValue(value);
64
+ }
65
+
66
+ function normalizeLicense(value) {
67
+ const raw = sanitizeValue(value);
68
+ if (raw === UNKNOWN_VALUE) return UNKNOWN_VALUE;
69
+
70
+ const lowered = raw.toLowerCase();
71
+ const canonicalKey = lowered.replace(/\s+/g, '').replace(/_/g, '-');
72
+ if (LICENSE_ALIASES[canonicalKey]) return LICENSE_ALIASES[canonicalKey];
73
+
74
+ const hyphenated = lowered.replace(/\s+/g, '-').replace(/_/g, '-');
75
+ const aliasByHyphen = LICENSE_ALIASES[hyphenated];
76
+ if (aliasByHyphen) return aliasByHyphen;
77
+
78
+ return hyphenated;
79
+ }
80
+
81
+ function extractVersionFromIdentifier(identifier) {
82
+ const text = asString(identifier);
83
+ if (!text) return UNKNOWN_VALUE;
84
+
85
+ if (text.includes(':')) {
86
+ const [, tag] = text.split(':');
87
+ return sanitizeValue(tag);
88
+ }
89
+
90
+ return UNKNOWN_VALUE;
91
+ }
92
+
93
+ function extractProvenance(model = {}, defaults = {}) {
94
+ const source = normalizeSource(
95
+ model?.provenance?.source ||
96
+ model?.source ||
97
+ defaults?.source
98
+ );
99
+
100
+ const registry = normalizeRegistry(
101
+ model?.provenance?.registry ||
102
+ model?.registry ||
103
+ model?.source_registry ||
104
+ defaults?.registry,
105
+ source
106
+ );
107
+
108
+ const version = normalizeVersion(
109
+ model?.provenance?.version ||
110
+ model?.version ||
111
+ model?.tag ||
112
+ model?.model_tag ||
113
+ defaults?.version ||
114
+ extractVersionFromIdentifier(
115
+ model?.model_identifier ||
116
+ model?.modelIdentifier ||
117
+ model?.identifier ||
118
+ model?.model_id ||
119
+ model?.modelId
120
+ )
121
+ );
122
+
123
+ const license = normalizeLicense(
124
+ model?.provenance?.license ||
125
+ model?.license ||
126
+ model?.license_id ||
127
+ model?.licenseId ||
128
+ defaults?.license
129
+ );
130
+
131
+ const digest = normalizeDigest(
132
+ model?.provenance?.digest ||
133
+ model?.digest ||
134
+ model?.hash ||
135
+ model?.sha256 ||
136
+ defaults?.digest
137
+ );
138
+
139
+ return {
140
+ source,
141
+ registry,
142
+ version,
143
+ license,
144
+ digest
145
+ };
146
+ }
147
+
148
+ function attachModelProvenance(model, defaults = {}) {
149
+ if (!model || typeof model !== 'object' || Array.isArray(model)) {
150
+ return model;
151
+ }
152
+
153
+ const provenance = extractProvenance(model, defaults);
154
+
155
+ return {
156
+ ...model,
157
+ source: provenance.source,
158
+ license: provenance.license,
159
+ version: provenance.version,
160
+ digest: provenance.digest,
161
+ provenance
162
+ };
163
+ }
164
+
165
+ function attachProvenanceToCollection(models, defaults = {}) {
166
+ if (!Array.isArray(models)) return [];
167
+ return models.map((model) => attachModelProvenance(model, defaults));
168
+ }
169
+
170
+ module.exports = {
171
+ UNKNOWN_VALUE,
172
+ normalizeLicense,
173
+ extractProvenance,
174
+ attachModelProvenance,
175
+ attachProvenanceToCollection
176
+ };
@@ -0,0 +1,174 @@
1
+ const SUPPORTED_RUNTIMES = ['ollama', 'vllm', 'mlx'];
2
+
3
+ function normalizeRuntime(runtime = 'ollama') {
4
+ const normalized = String(runtime || 'ollama').trim().toLowerCase();
5
+ return SUPPORTED_RUNTIMES.includes(normalized) ? normalized : 'ollama';
6
+ }
7
+
8
+ function getRuntimeDisplayName(runtime = 'ollama') {
9
+ const normalized = normalizeRuntime(runtime);
10
+ if (normalized === 'vllm') return 'vLLM';
11
+ if (normalized === 'mlx') return 'MLX-LM';
12
+ return 'Ollama';
13
+ }
14
+
15
+ function isAppleSiliconHardware(hardware = {}) {
16
+ const osPlatform = String(hardware?.os?.platform || '').toLowerCase();
17
+ const arch = String(
18
+ hardware?.cpu?.architecture ||
19
+ hardware?.summary?.architecture ||
20
+ ''
21
+ ).toLowerCase();
22
+ const cpuBrand = String(hardware?.cpu?.brand || '').toLowerCase();
23
+ const gpuModel = String(hardware?.gpu?.model || '').toLowerCase();
24
+ const isDarwin = osPlatform === 'darwin' || osPlatform === 'macos';
25
+ const hasAppleChipSignal =
26
+ arch.includes('apple silicon') ||
27
+ cpuBrand.includes('apple') ||
28
+ gpuModel.includes('apple');
29
+
30
+ // Prefer explicit Apple signals and avoid treating generic Linux ARM64 as Apple Silicon.
31
+ if (isDarwin) {
32
+ return arch === 'arm64' || hasAppleChipSignal;
33
+ }
34
+
35
+ // Fallback for partial hardware payloads that still expose Apple-specific identifiers.
36
+ return hasAppleChipSignal;
37
+ }
38
+
39
+ function runtimeSupportedOnHardware(runtime = 'ollama', hardware = {}) {
40
+ const normalized = normalizeRuntime(runtime);
41
+ if (normalized === 'mlx') {
42
+ return isAppleSiliconHardware(hardware);
43
+ }
44
+ return true;
45
+ }
46
+
47
+ function runtimeSupportsSpeculativeDecoding(runtime = 'ollama') {
48
+ const normalized = normalizeRuntime(runtime);
49
+ return normalized === 'vllm' || normalized === 'mlx';
50
+ }
51
+
52
+ function shellEscapeArg(value = '') {
53
+ const text = String(value || '');
54
+ if (!text) return "''";
55
+ return `'${text.replace(/'/g, `'\\''`)}'`;
56
+ }
57
+
58
+ function extractFromInstallCommand(command = '') {
59
+ const match = String(command).match(/ollama\s+pull\s+(.+)$/i);
60
+ return match ? match[1].trim() : '';
61
+ }
62
+
63
+ function slugifyModelName(text = '') {
64
+ return String(text)
65
+ .toLowerCase()
66
+ .replace(/[^a-z0-9]+/g, '-')
67
+ .replace(/^-+|-+$/g, '') || 'model';
68
+ }
69
+
70
+ function getRuntimeModelRef(model = {}, runtime = 'ollama') {
71
+ const normalized = normalizeRuntime(runtime);
72
+
73
+ const candidates = [
74
+ model.hfModel,
75
+ model.hfId,
76
+ model.huggingfaceId,
77
+ model.model_identifier,
78
+ model.identifier,
79
+ model.cloudData?.identifier,
80
+ model.ollamaTag,
81
+ extractFromInstallCommand(model.installation?.ollama),
82
+ model.ollamaId,
83
+ model.name
84
+ ].filter(Boolean);
85
+
86
+ const raw = String(candidates[0] || '').trim();
87
+ if (!raw) return 'model';
88
+
89
+ if (normalized === 'ollama') {
90
+ return raw;
91
+ }
92
+
93
+ if (raw.includes('/')) {
94
+ return raw;
95
+ }
96
+
97
+ // Remove Ollama-style tag suffix for non-Ollama runtimes.
98
+ const base = raw.split(':')[0].trim();
99
+ if (base) {
100
+ return /\s/.test(base) ? slugifyModelName(base) : base;
101
+ }
102
+
103
+ return slugifyModelName(raw);
104
+ }
105
+
106
+ function getRuntimeInstallCommand(runtime = 'ollama') {
107
+ const normalized = normalizeRuntime(runtime);
108
+
109
+ if (normalized === 'vllm') {
110
+ return 'pip install -U "vllm>=0.6.0"';
111
+ }
112
+
113
+ if (normalized === 'mlx') {
114
+ return 'pip install -U mlx-lm';
115
+ }
116
+
117
+ return 'ollama --version || (brew install ollama)';
118
+ }
119
+
120
+ function getRuntimePullCommand(model = {}, runtime = 'ollama') {
121
+ const normalized = normalizeRuntime(runtime);
122
+ const modelRef = getRuntimeModelRef(model, runtime);
123
+
124
+ if (normalized === 'vllm') {
125
+ return `huggingface-cli download ${shellEscapeArg(modelRef)}`;
126
+ }
127
+
128
+ if (normalized === 'mlx') {
129
+ const localName = slugifyModelName(modelRef);
130
+ return `python -m mlx_lm.convert --hf-path ${shellEscapeArg(modelRef)} --mlx-path ./models/${localName}`;
131
+ }
132
+
133
+ return `ollama pull ${modelRef}`;
134
+ }
135
+
136
+ function getRuntimeRunCommand(model = {}, runtime = 'ollama') {
137
+ const normalized = normalizeRuntime(runtime);
138
+ const modelRef = getRuntimeModelRef(model, runtime);
139
+
140
+ if (normalized === 'vllm') {
141
+ return `python -m vllm.entrypoints.openai.api_server --model ${shellEscapeArg(modelRef)} --host 0.0.0.0 --port 8000`;
142
+ }
143
+
144
+ if (normalized === 'mlx') {
145
+ return `python -m mlx_lm.generate --model ${shellEscapeArg(modelRef)} --prompt "Hello"`;
146
+ }
147
+
148
+ return `ollama run ${modelRef}`;
149
+ }
150
+
151
+ function getRuntimeCommandSet(model = {}, runtime = 'ollama') {
152
+ const normalized = normalizeRuntime(runtime);
153
+ return {
154
+ runtime: normalized,
155
+ displayName: getRuntimeDisplayName(normalized),
156
+ modelRef: getRuntimeModelRef(model, normalized),
157
+ install: getRuntimeInstallCommand(normalized),
158
+ pull: getRuntimePullCommand(model, normalized),
159
+ run: getRuntimeRunCommand(model, normalized)
160
+ };
161
+ }
162
+
163
+ module.exports = {
164
+ SUPPORTED_RUNTIMES,
165
+ normalizeRuntime,
166
+ getRuntimeDisplayName,
167
+ runtimeSupportedOnHardware,
168
+ runtimeSupportsSpeculativeDecoding,
169
+ getRuntimeModelRef,
170
+ getRuntimeInstallCommand,
171
+ getRuntimePullCommand,
172
+ getRuntimeRunCommand,
173
+ getRuntimeCommandSet
174
+ };
package/bin/CLAUDE.md DELETED
@@ -1,27 +0,0 @@
1
- <claude-mem-context>
2
- # Recent Activity
3
-
4
- <!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
5
-
6
- ### Feb 12, 2026
7
-
8
- | ID | Time | T | Title | Read |
9
- |----|------|---|-------|------|
10
- | #3492 | 10:24 PM | 🔵 | Enhanced CLI Structure - Lazy Loading with ASCII Art Branding | ~456 |
11
- | #3436 | 9:57 PM | 🔵 | Enhanced CLI Implementation - Command-Line Interface with ASCII Art and Ollama Integration | ~575 |
12
-
13
- ### Feb 14, 2026
14
-
15
- | ID | Time | T | Title | Read |
16
- |----|------|---|-------|------|
17
- | #4369 | 7:19 PM | 🟣 | Added 6 advanced MCP-exclusive tools for benchmarking, optimization, and intelligent model management | ~703 |
18
- | #4368 | " | 🟣 | MCP server implementation added to llm-checker with benchmark tool | ~387 |
19
- | #4363 | 7:18 PM | 🟣 | MCP tools test suite successfully validates all 7 llm-checker tools | ~551 |
20
- | #4361 | 7:17 PM | 🟣 | MCP server test suite created for validating Ollama integration tools | ~524 |
21
- | #4356 | " | 🟣 | Six advanced MCP tools added to llm-checker server beyond CLI wrapping | ~750 |
22
- | #4349 | 7:15 PM | 🟣 | MCP server implementation exposes 9 tools for LLM hardware analysis and Ollama model management | ~539 |
23
- | #4341 | 6:49 PM | 🟣 | MCP server implementation committed to llm-checker repository | ~431 |
24
- | #4339 | " | 🟣 | MCP server implementation and documentation added to llm-checker repository | ~457 |
25
- | #4338 | " | ✅ | MCP server validated in llm-checker repository structure | ~245 |
26
- | #4333 | 6:48 PM | 🟣 | MCP server integrated into llm-checker package distribution | ~409 |
27
- </claude-mem-context>