@sheepbun/yips 0.1.1 → 0.1.47

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.
Files changed (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +52 -0
  3. package/bin/yips.js +15 -0
  4. package/package.json +21 -128
  5. package/postinstall.js +52 -0
  6. package/dist/agent/commands/command-catalog.js +0 -243
  7. package/dist/agent/commands/commands.js +0 -418
  8. package/dist/agent/conductor.js +0 -118
  9. package/dist/agent/context/code-context.js +0 -68
  10. package/dist/agent/context/memory-store.js +0 -159
  11. package/dist/agent/context/session-store.js +0 -211
  12. package/dist/agent/protocol/tool-protocol.js +0 -160
  13. package/dist/agent/skills/skills.js +0 -327
  14. package/dist/agent/tools/tool-executor.js +0 -415
  15. package/dist/agent/tools/tool-safety.js +0 -52
  16. package/dist/app/index.js +0 -35
  17. package/dist/app/repl.js +0 -105
  18. package/dist/app/update-check.js +0 -132
  19. package/dist/app/version.js +0 -51
  20. package/dist/code-context.js +0 -68
  21. package/dist/colors.js +0 -204
  22. package/dist/command-catalog.js +0 -242
  23. package/dist/commands.js +0 -350
  24. package/dist/conductor.js +0 -94
  25. package/dist/config/config.js +0 -335
  26. package/dist/config/hooks.js +0 -187
  27. package/dist/config.js +0 -335
  28. package/dist/downloader-state.js +0 -302
  29. package/dist/downloader-ui.js +0 -289
  30. package/dist/gateway/adapters/discord.js +0 -108
  31. package/dist/gateway/adapters/formatting.js +0 -96
  32. package/dist/gateway/adapters/telegram.js +0 -106
  33. package/dist/gateway/adapters/types.js +0 -2
  34. package/dist/gateway/adapters/whatsapp.js +0 -124
  35. package/dist/gateway/auth-policy.js +0 -66
  36. package/dist/gateway/core.js +0 -87
  37. package/dist/gateway/headless-conductor.js +0 -328
  38. package/dist/gateway/message-router.js +0 -23
  39. package/dist/gateway/rate-limiter.js +0 -48
  40. package/dist/gateway/runtime/backend-policy.js +0 -18
  41. package/dist/gateway/runtime/discord-bot.js +0 -104
  42. package/dist/gateway/runtime/discord-main.js +0 -69
  43. package/dist/gateway/session-manager.js +0 -77
  44. package/dist/gateway/types.js +0 -2
  45. package/dist/hardware.js +0 -92
  46. package/dist/hooks.js +0 -187
  47. package/dist/index.js +0 -34
  48. package/dist/input-engine.js +0 -250
  49. package/dist/llama-client.js +0 -227
  50. package/dist/llama-server.js +0 -620
  51. package/dist/llm/llama-client.js +0 -227
  52. package/dist/llm/llama-server.js +0 -620
  53. package/dist/llm/token-counter.js +0 -47
  54. package/dist/memory-store.js +0 -159
  55. package/dist/messages.js +0 -59
  56. package/dist/model-downloader.js +0 -382
  57. package/dist/model-manager-state.js +0 -118
  58. package/dist/model-manager-ui.js +0 -194
  59. package/dist/model-manager.js +0 -190
  60. package/dist/models/hardware.js +0 -92
  61. package/dist/models/model-downloader.js +0 -382
  62. package/dist/models/model-manager.js +0 -190
  63. package/dist/prompt-box.js +0 -78
  64. package/dist/prompt-composer.js +0 -498
  65. package/dist/repl.js +0 -105
  66. package/dist/session-store.js +0 -211
  67. package/dist/spinner.js +0 -76
  68. package/dist/title-box.js +0 -388
  69. package/dist/token-counter.js +0 -47
  70. package/dist/tool-executor.js +0 -415
  71. package/dist/tool-protocol.js +0 -121
  72. package/dist/tool-safety.js +0 -52
  73. package/dist/tui/app.js +0 -2553
  74. package/dist/tui/startup.js +0 -56
  75. package/dist/tui-input-routing.js +0 -53
  76. package/dist/tui.js +0 -51
  77. package/dist/types/app-types.js +0 -2
  78. package/dist/types.js +0 -2
  79. package/dist/ui/colors.js +0 -204
  80. package/dist/ui/downloader/downloader-state.js +0 -302
  81. package/dist/ui/downloader/downloader-ui.js +0 -289
  82. package/dist/ui/input/input-engine.js +0 -250
  83. package/dist/ui/input/tui-input-routing.js +0 -53
  84. package/dist/ui/input/vt-session.js +0 -168
  85. package/dist/ui/messages.js +0 -59
  86. package/dist/ui/model-manager/model-manager-state.js +0 -118
  87. package/dist/ui/model-manager/model-manager-ui.js +0 -194
  88. package/dist/ui/prompt/prompt-box.js +0 -78
  89. package/dist/ui/prompt/prompt-composer.js +0 -498
  90. package/dist/ui/spinner.js +0 -76
  91. package/dist/ui/title-box.js +0 -388
  92. package/dist/ui/tui/app.js +0 -6
  93. package/dist/ui/tui/autocomplete.js +0 -85
  94. package/dist/ui/tui/constants.js +0 -18
  95. package/dist/ui/tui/history.js +0 -29
  96. package/dist/ui/tui/layout.js +0 -341
  97. package/dist/ui/tui/runtime-core.js +0 -2584
  98. package/dist/ui/tui/runtime-utils.js +0 -53
  99. package/dist/ui/tui/start-tui.js +0 -54
  100. package/dist/ui/tui/startup.js +0 -56
  101. package/dist/version.js +0 -51
  102. package/dist/vt-session.js +0 -168
  103. package/install.sh +0 -457
@@ -1,382 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resolveDefaultModelsDir = resolveDefaultModelsDir;
4
- exports.isHfDownloadUrl = isHfDownloadUrl;
5
- exports.parseHfDownloadUrl = parseHfDownloadUrl;
6
- exports.listGgufModels = listGgufModels;
7
- exports.listModelFiles = listModelFiles;
8
- exports.downloadModelFile = downloadModelFile;
9
- exports.renderModelList = renderModelList;
10
- exports.renderFileList = renderFileList;
11
- const node_fs_1 = require("node:fs");
12
- const promises_1 = require("node:fs/promises");
13
- const node_os_1 = require("node:os");
14
- const node_path_1 = require("node:path");
15
- function encodePath(path) {
16
- return path
17
- .split("/")
18
- .map((segment) => encodeURIComponent(segment))
19
- .join("/");
20
- }
21
- function toPositiveInt(value, fallback = 0) {
22
- if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
23
- return fallback;
24
- }
25
- return Math.floor(value);
26
- }
27
- function toDateText(value) {
28
- if (typeof value !== "string") {
29
- return "unknown";
30
- }
31
- const trimmed = value.trim();
32
- return trimmed.length > 0 ? trimmed : "unknown";
33
- }
34
- function safeRelativePath(path) {
35
- const normalized = (0, node_path_1.normalize)(path).replace(/^[\\/]+/u, "");
36
- const segments = normalized.split(/[\\/]/u).filter((segment) => segment.length > 0);
37
- if (segments.some((segment) => segment === "..")) {
38
- throw new Error(`Unsafe filename: ${path}`);
39
- }
40
- if (segments.length === 0) {
41
- throw new Error("Filename cannot be empty.");
42
- }
43
- return segments.join("/");
44
- }
45
- function extractSizeBytes(value) {
46
- if (typeof value === "number" && Number.isFinite(value) && value > 0) {
47
- return value;
48
- }
49
- if (typeof value !== "object" || value === null) {
50
- return null;
51
- }
52
- const source = value;
53
- const direct = source.total;
54
- if (typeof direct === "number" && Number.isFinite(direct) && direct > 0) {
55
- return direct;
56
- }
57
- const lfs = source.lfs;
58
- if (typeof lfs === "object" && lfs !== null) {
59
- const lfsSize = lfs.size;
60
- if (typeof lfsSize === "number" && Number.isFinite(lfsSize) && lfsSize > 0) {
61
- return lfsSize;
62
- }
63
- }
64
- return null;
65
- }
66
- function evaluateSuitability(sizeBytes, totalMemoryGb) {
67
- if (sizeBytes === null) {
68
- return { canRun: true, reason: "Unknown size" };
69
- }
70
- if (typeof totalMemoryGb !== "number" || !Number.isFinite(totalMemoryGb) || totalMemoryGb <= 0) {
71
- return { canRun: true, reason: "No memory limit" };
72
- }
73
- const memoryLimitMultiplier = 1.2;
74
- const sizeGb = sizeBytes / (1024 * 1024 * 1024);
75
- const effectiveLimit = totalMemoryGb * memoryLimitMultiplier;
76
- if (sizeGb <= effectiveLimit) {
77
- return { canRun: true, reason: "Fits RAM+VRAM" };
78
- }
79
- return {
80
- canRun: false,
81
- reason: `Model too large (${sizeGb.toFixed(1)}GB > ${effectiveLimit.toFixed(1)}GB)`
82
- };
83
- }
84
- function extractQuant(path) {
85
- const upper = path.toUpperCase();
86
- if (upper.includes("Q4_K_M"))
87
- return "Q4_K_M (Balanced)";
88
- if (upper.includes("Q5_K_M"))
89
- return "Q5_K_M (High Quality)";
90
- if (upper.includes("Q8_0"))
91
- return "Q8_0 (Max Quality)";
92
- if (upper.includes("Q2_K"))
93
- return "Q2_K (Max Speed)";
94
- const match = upper.match(/(Q\d+_[A-Z0-9_]+)/u);
95
- return match?.[1] ?? "Unknown";
96
- }
97
- async function readJson(response) {
98
- if (!response.ok) {
99
- throw new Error(`HTTP ${response.status} ${response.statusText}`);
100
- }
101
- return (await response.json());
102
- }
103
- function getFetch(fetchImpl) {
104
- return fetchImpl ?? fetch;
105
- }
106
- function resolveDefaultModelsDir() {
107
- const env = process.env["YIPS_MODELS_DIR"]?.trim();
108
- if (env && env.length > 0) {
109
- return env;
110
- }
111
- return (0, node_path_1.join)((0, node_os_1.homedir)(), ".yips", "models");
112
- }
113
- function isHfDownloadUrl(input) {
114
- try {
115
- parseHfDownloadUrl(input);
116
- return true;
117
- }
118
- catch {
119
- return false;
120
- }
121
- }
122
- function parseHfDownloadUrl(input) {
123
- const raw = input.trim();
124
- if (raw.length === 0) {
125
- throw new Error("URL is required.");
126
- }
127
- let url;
128
- try {
129
- url = new URL(raw);
130
- }
131
- catch {
132
- throw new Error("Invalid URL.");
133
- }
134
- const host = url.hostname.toLowerCase();
135
- if (host !== "hf.co" && host !== "huggingface.co" && host !== "www.huggingface.co") {
136
- throw new Error("URL must be from hf.co or huggingface.co.");
137
- }
138
- const segments = url.pathname.split("/").filter((segment) => segment.length > 0);
139
- if (segments.length < 5) {
140
- throw new Error("URL must include /<owner>/<repo>/resolve/<revision>/<file>.gguf");
141
- }
142
- const resolveIndex = segments.findIndex((segment) => segment === "resolve");
143
- if (resolveIndex !== 2 || segments.length < 5) {
144
- throw new Error("URL must include /<owner>/<repo>/resolve/<revision>/<file>.gguf");
145
- }
146
- const owner = segments[0] ?? "";
147
- const repo = segments[1] ?? "";
148
- const revision = segments[3] ?? "main";
149
- const filenameSegments = segments.slice(4);
150
- const filename = filenameSegments.join("/");
151
- if (!filename.toLowerCase().endsWith(".gguf")) {
152
- throw new Error("URL must point to a .gguf file.");
153
- }
154
- return {
155
- repoId: `${owner}/${repo}`,
156
- revision,
157
- filename
158
- };
159
- }
160
- async function listGgufModels(options) {
161
- const query = options?.query?.trim() ?? "";
162
- const sort = options?.sort ?? "downloads";
163
- const limit = Math.max(1, Math.min(100, options?.limit ?? 10));
164
- const requestModels = async (expanded) => {
165
- const url = new URL("https://huggingface.co/api/models");
166
- url.searchParams.set("filter", "gguf");
167
- url.searchParams.set("sort", sort);
168
- url.searchParams.set("limit", String(limit));
169
- if (expanded) {
170
- url.searchParams.append("expand", "downloads");
171
- url.searchParams.append("expand", "likes");
172
- url.searchParams.append("expand", "lastModified");
173
- url.searchParams.append("expand", "gguf");
174
- }
175
- if (query.length > 0) {
176
- url.searchParams.set("search", query);
177
- }
178
- const response = await getFetch(options?.fetchImpl)(url);
179
- return readJson(response);
180
- };
181
- let data;
182
- try {
183
- data = await requestModels(true);
184
- }
185
- catch (error) {
186
- const message = error instanceof Error ? error.message : String(error);
187
- if (!message.includes("HTTP 400") && !message.includes("HTTP 422")) {
188
- throw error;
189
- }
190
- data = await requestModels(false);
191
- }
192
- return data
193
- .map((item) => {
194
- if (typeof item !== "object" || item === null) {
195
- return null;
196
- }
197
- const model = item;
198
- const id = typeof model.id === "string" ? model.id : "";
199
- if (id.trim().length === 0) {
200
- return null;
201
- }
202
- const sizeBytes = extractSizeBytes(model.gguf);
203
- const suitability = evaluateSuitability(sizeBytes, options?.totalMemoryGb);
204
- return {
205
- id,
206
- downloads: toPositiveInt(model.downloads),
207
- likes: toPositiveInt(model.likes),
208
- lastModified: toDateText(model.lastModified),
209
- sizeBytes,
210
- canRun: suitability.canRun,
211
- reason: suitability.reason
212
- };
213
- })
214
- .filter((entry) => entry !== null)
215
- .filter((entry) => entry.canRun || entry.sizeBytes === null);
216
- }
217
- async function listModelFiles(repoId, options) {
218
- const trimmedRepo = repoId.trim();
219
- if (trimmedRepo.length === 0) {
220
- throw new Error("Repository id is required.");
221
- }
222
- const encodedRepo = encodePath(trimmedRepo);
223
- const url = new URL(`https://huggingface.co/api/models/${encodedRepo}`);
224
- url.searchParams.set("blobs", "true");
225
- const response = await getFetch(options?.fetchImpl)(url);
226
- const data = await readJson(response);
227
- const siblingsValue = data.siblings;
228
- const siblings = Array.isArray(siblingsValue) ? siblingsValue : [];
229
- const files = siblings
230
- .map((item) => {
231
- if (typeof item !== "object" || item === null) {
232
- return null;
233
- }
234
- const file = item;
235
- const path = typeof file.rfilename === "string" ? file.rfilename : "";
236
- if (!path.toLowerCase().endsWith(".gguf")) {
237
- return null;
238
- }
239
- const sizeBytes = extractSizeBytes(file.size ?? file.lfs ?? null);
240
- const suitability = evaluateSuitability(sizeBytes, options?.totalMemoryGb);
241
- return {
242
- path,
243
- sizeBytes,
244
- quant: extractQuant(path),
245
- canRun: suitability.canRun,
246
- reason: suitability.reason
247
- };
248
- })
249
- .filter((entry) => entry !== null);
250
- files.sort((left, right) => {
251
- const leftSize = left.sizeBytes ?? Number.MAX_SAFE_INTEGER;
252
- const rightSize = right.sizeBytes ?? Number.MAX_SAFE_INTEGER;
253
- if (leftSize !== rightSize) {
254
- return leftSize - rightSize;
255
- }
256
- return left.path.localeCompare(right.path);
257
- });
258
- return files;
259
- }
260
- async function downloadModelFile(options) {
261
- const repoId = options.repoId.trim();
262
- if (repoId.length === 0) {
263
- throw new Error("Repository id is required.");
264
- }
265
- const safeFilename = safeRelativePath(options.filename.trim());
266
- const modelsDir = options.modelsDir?.trim() || resolveDefaultModelsDir();
267
- const repoPath = safeRelativePath(repoId);
268
- const outputPath = (0, node_path_1.resolve)(modelsDir, repoPath, safeFilename);
269
- const revision = options.revision?.trim() || "main";
270
- await (0, promises_1.mkdir)((0, node_path_1.dirname)(outputPath), { recursive: true });
271
- let byteCount = 0;
272
- let completed = false;
273
- try {
274
- const fileUrl = `https://huggingface.co/${encodePath(repoId)}/resolve/${encodeURIComponent(revision)}/` +
275
- `${encodePath(safeFilename)}?download=true`;
276
- const response = await getFetch(options.fetchImpl)(fileUrl, { signal: options.signal });
277
- if (!response.ok) {
278
- throw new Error(`Download failed with HTTP ${response.status} ${response.statusText}`);
279
- }
280
- if (!response.body) {
281
- throw new Error("Download failed: response body is empty.");
282
- }
283
- const contentLengthHeader = response.headers.get("content-length");
284
- const parsedLength = contentLengthHeader
285
- ? Number.parseInt(contentLengthHeader, 10)
286
- : Number.NaN;
287
- const totalBytes = Number.isFinite(parsedLength) && parsedLength > 0 ? parsedLength : null;
288
- const emitProgress = (bytesDownloaded) => {
289
- if (!options.onProgress) {
290
- return;
291
- }
292
- options.onProgress({
293
- bytesDownloaded,
294
- totalBytes
295
- });
296
- };
297
- emitProgress(byteCount);
298
- await new Promise((resolvePromise, rejectPromise) => {
299
- const writer = (0, node_fs_1.createWriteStream)(outputPath);
300
- writer.on("error", rejectPromise);
301
- writer.on("finish", resolvePromise);
302
- const reader = response.body?.getReader();
303
- if (!reader) {
304
- writer.destroy(new Error("Download failed: could not create stream reader."));
305
- return;
306
- }
307
- const pump = async () => {
308
- try {
309
- let reading = true;
310
- while (reading) {
311
- const chunk = await reader.read();
312
- if (chunk.done) {
313
- writer.end();
314
- reading = false;
315
- continue;
316
- }
317
- const value = chunk.value;
318
- byteCount += value.byteLength;
319
- emitProgress(byteCount);
320
- if (!writer.write(Buffer.from(value))) {
321
- await new Promise((drainResolve) => {
322
- writer.once("drain", drainResolve);
323
- });
324
- }
325
- }
326
- }
327
- catch (error) {
328
- writer.destroy(error instanceof Error ? error : new Error(String(error)));
329
- }
330
- };
331
- void pump();
332
- });
333
- emitProgress(byteCount);
334
- completed = true;
335
- return { localPath: outputPath, byteCount };
336
- }
337
- catch (error) {
338
- if (!completed) {
339
- try {
340
- await (0, promises_1.rm)(outputPath, { force: true });
341
- }
342
- catch {
343
- // Preserve the original download error when cleanup fails.
344
- }
345
- }
346
- throw error;
347
- }
348
- }
349
- function formatCount(value) {
350
- if (value >= 1_000_000) {
351
- return `${(value / 1_000_000).toFixed(1)}M`;
352
- }
353
- if (value >= 1_000) {
354
- return `${(value / 1_000).toFixed(1)}k`;
355
- }
356
- return String(value);
357
- }
358
- function formatSize(bytes) {
359
- if (bytes === null || bytes <= 0) {
360
- return "unknown";
361
- }
362
- const gib = bytes / (1024 * 1024 * 1024);
363
- return `${gib.toFixed(2)} GB`;
364
- }
365
- function renderModelList(models) {
366
- if (models.length === 0) {
367
- return "No GGUF models found.";
368
- }
369
- const rows = models.map((model, index) => {
370
- const date = model.lastModified === "unknown" ? "unknown" : model.lastModified.slice(0, 10);
371
- return `${index + 1}. ${model.id} (downloads ${formatCount(model.downloads)}, likes ${formatCount(model.likes)}, size ${formatSize(model.sizeBytes)}, updated ${date})`;
372
- });
373
- return rows.join("\n");
374
- }
375
- function renderFileList(files) {
376
- if (files.length === 0) {
377
- return "No GGUF files found for this repository.";
378
- }
379
- return files
380
- .map((file, index) => `${index + 1}. ${file.path} (${formatSize(file.sizeBytes)}, ${file.quant}, ${file.reason})`)
381
- .join("\n");
382
- }
@@ -1,190 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getModelDisplayName = getModelDisplayName;
4
- exports.getFriendlyModelName = getFriendlyModelName;
5
- exports.listLocalModels = listLocalModels;
6
- exports.filterModels = filterModels;
7
- exports.findMatchingModel = findMatchingModel;
8
- exports.selectBestModelForHardware = selectBestModelForHardware;
9
- exports.deleteLocalModel = deleteLocalModel;
10
- const promises_1 = require("node:fs/promises");
11
- const node_path_1 = require("node:path");
12
- const model_downloader_1 = require("#models/model-downloader");
13
- const MEMORY_LIMIT_MULTIPLIER = 1.2;
14
- function evaluateSuitability(sizeBytes, totalMemoryGb) {
15
- if (typeof totalMemoryGb !== "number" || !Number.isFinite(totalMemoryGb) || totalMemoryGb <= 0) {
16
- return { canRun: true, reason: "No memory limit" };
17
- }
18
- const sizeGb = sizeBytes / 1024 ** 3;
19
- const effectiveLimit = totalMemoryGb * MEMORY_LIMIT_MULTIPLIER;
20
- if (sizeGb <= effectiveLimit) {
21
- return { canRun: true, reason: "Fits RAM+VRAM" };
22
- }
23
- return {
24
- canRun: false,
25
- reason: `Model too large (${sizeGb.toFixed(1)}GB > ${effectiveLimit.toFixed(1)}GB)`
26
- };
27
- }
28
- function getModelDisplayName(modelId) {
29
- const normalizedId = modelId.replace(/\\/gu, "/");
30
- const segments = normalizedId.split("/").filter((segment) => segment.length > 0);
31
- const filename = (0, node_path_1.basename)(modelId);
32
- if (filename.toLowerCase().endsWith(".gguf")) {
33
- const parent = segments.length > 1 ? (segments[segments.length - 2] ?? "") : "";
34
- if (parent.length > 0) {
35
- return parent;
36
- }
37
- return filename.slice(0, -5);
38
- }
39
- return filename;
40
- }
41
- function getFriendlyModelName(modelId, nicknames) {
42
- const exact = nicknames[modelId];
43
- if (typeof exact === "string" && exact.trim().length > 0) {
44
- return exact.trim();
45
- }
46
- const fallback = getModelDisplayName(modelId);
47
- const byFilename = nicknames[fallback];
48
- if (typeof byFilename === "string" && byFilename.trim().length > 0) {
49
- return byFilename.trim();
50
- }
51
- const filename = (0, node_path_1.basename)(modelId);
52
- if (filename.toLowerCase().endsWith(".gguf")) {
53
- const filenameStem = filename.slice(0, -5);
54
- const byStem = nicknames[filenameStem];
55
- if (typeof byStem === "string" && byStem.trim().length > 0) {
56
- return byStem.trim();
57
- }
58
- }
59
- return fallback;
60
- }
61
- async function collectGgufPaths(root, base = root) {
62
- const entries = await (0, promises_1.readdir)(root, { withFileTypes: true });
63
- const paths = [];
64
- for (const entry of entries) {
65
- const absolutePath = (0, node_path_1.join)(root, entry.name);
66
- if (entry.isDirectory()) {
67
- paths.push(...(await collectGgufPaths(absolutePath, base)));
68
- continue;
69
- }
70
- if (!entry.isFile()) {
71
- continue;
72
- }
73
- if ((0, node_path_1.extname)(entry.name).toLowerCase() !== ".gguf") {
74
- continue;
75
- }
76
- const rel = (0, node_path_1.relative)(base, absolutePath).replace(/\\/gu, "/");
77
- paths.push(rel);
78
- }
79
- return paths;
80
- }
81
- async function listLocalModels(options) {
82
- const modelsDir = (0, node_path_1.resolve)(options?.modelsDir?.trim() || (0, model_downloader_1.resolveDefaultModelsDir)());
83
- const nicknames = options?.nicknames ?? {};
84
- let modelIds = [];
85
- try {
86
- modelIds = await collectGgufPaths(modelsDir);
87
- }
88
- catch {
89
- return [];
90
- }
91
- const rows = [];
92
- for (const modelId of modelIds.sort((left, right) => left.localeCompare(right))) {
93
- const path = (0, node_path_1.join)(modelsDir, modelId);
94
- let sizeBytes = 0;
95
- try {
96
- const stats = await (0, promises_1.stat)(path);
97
- sizeBytes = stats.isFile() ? stats.size : 0;
98
- }
99
- catch {
100
- continue;
101
- }
102
- const parts = modelId.split("/");
103
- const host = parts.length > 1 ? (parts[0] ?? "Local") : "Local";
104
- const suitability = evaluateSuitability(sizeBytes, options?.totalMemoryGb);
105
- rows.push({
106
- id: modelId,
107
- name: getModelDisplayName(modelId),
108
- friendlyName: getFriendlyModelName(modelId, nicknames),
109
- host,
110
- backend: "llamacpp",
111
- friendlyBackend: "llama.cpp",
112
- sizeBytes,
113
- sizeGb: sizeBytes / 1024 ** 3,
114
- canRun: suitability.canRun,
115
- reason: suitability.reason,
116
- path
117
- });
118
- }
119
- return rows;
120
- }
121
- function filterModels(models, query) {
122
- const trimmed = query.trim().toLowerCase();
123
- if (trimmed.length === 0) {
124
- return [...models];
125
- }
126
- return models.filter((model) => [model.id, model.name, model.friendlyName, model.host].some((value) => value.toLowerCase().includes(trimmed)));
127
- }
128
- function findMatchingModel(models, input) {
129
- const needle = input.trim().toLowerCase();
130
- if (needle.length === 0) {
131
- return null;
132
- }
133
- const exact = models.find((model) => model.id.toLowerCase() === needle ||
134
- model.name.toLowerCase() === needle ||
135
- (0, node_path_1.basename)(model.id).toLowerCase() === needle);
136
- if (exact) {
137
- return exact;
138
- }
139
- const partial = models.find((model) => model.id.toLowerCase().includes(needle) ||
140
- model.name.toLowerCase().includes(needle) ||
141
- (0, node_path_1.basename)(model.id).toLowerCase().includes(needle));
142
- return partial ?? null;
143
- }
144
- function byLargestSize(left, right) {
145
- if (left.sizeBytes !== right.sizeBytes) {
146
- return right.sizeBytes - left.sizeBytes;
147
- }
148
- return left.id.localeCompare(right.id);
149
- }
150
- function selectBestModelForHardware(models, specs) {
151
- const runnable = models.filter((model) => model.canRun);
152
- if (runnable.length === 0) {
153
- return null;
154
- }
155
- const vramBytes = Number.isFinite(specs.vramGb) && specs.vramGb > 0 ? specs.vramGb * 1024 ** 3 : 0;
156
- if (vramBytes > 0) {
157
- const gpuFit = runnable.filter((model) => model.sizeBytes <= vramBytes);
158
- if (gpuFit.length > 0) {
159
- return [...gpuFit].sort(byLargestSize)[0] ?? null;
160
- }
161
- }
162
- return [...runnable].sort(byLargestSize)[0] ?? null;
163
- }
164
- async function pruneEmptyParents(path, root) {
165
- let current = (0, node_path_1.dirname)(path);
166
- const normalizedRoot = (0, node_path_1.resolve)(root);
167
- while (current.startsWith(normalizedRoot) && current !== normalizedRoot) {
168
- try {
169
- const entries = await (0, promises_1.readdir)(current);
170
- if (entries.length > 0) {
171
- return;
172
- }
173
- await (0, promises_1.rmdir)(current);
174
- current = (0, node_path_1.dirname)(current);
175
- }
176
- catch {
177
- return;
178
- }
179
- }
180
- }
181
- async function deleteLocalModel(model, options) {
182
- const modelsDir = (0, node_path_1.resolve)(options?.modelsDir?.trim() || (0, model_downloader_1.resolveDefaultModelsDir)());
183
- const modelPath = (0, node_path_1.resolve)(model.path);
184
- const expectedPrefix = `${modelsDir}${node_path_1.sep}`;
185
- if (modelPath !== modelsDir && !modelPath.startsWith(expectedPrefix)) {
186
- throw new Error("Refusing to delete model outside models directory.");
187
- }
188
- await (0, promises_1.unlink)(modelPath);
189
- await pruneEmptyParents(modelPath, modelsDir);
190
- }
@@ -1,78 +0,0 @@
1
- "use strict";
2
- /** Prompt box layout helpers for the bottom input area. */
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.buildPromptBoxFrame = buildPromptBoxFrame;
5
- exports.buildPromptBoxLayout = buildPromptBoxLayout;
6
- const DEFAULT_PROMPT = ">>> ";
7
- function toChars(text) {
8
- return Array.from(text);
9
- }
10
- function charLength(text) {
11
- return toChars(text).length;
12
- }
13
- function takeLeftChars(text, maxWidth) {
14
- if (maxWidth <= 0)
15
- return "";
16
- const chars = toChars(text);
17
- if (chars.length <= maxWidth)
18
- return text;
19
- return chars.slice(0, maxWidth).join("");
20
- }
21
- function takeRightChars(text, maxWidth) {
22
- if (maxWidth <= 0)
23
- return "";
24
- const chars = toChars(text);
25
- if (chars.length <= maxWidth)
26
- return text;
27
- return chars.slice(chars.length - maxWidth).join("");
28
- }
29
- function buildBorderLine(width, left, right, fill) {
30
- if (width <= 0)
31
- return "";
32
- if (width === 1)
33
- return left;
34
- return `${left}${fill.repeat(Math.max(0, width - 2))}${right}`;
35
- }
36
- function buildContentLine(width, left, right, inner) {
37
- if (width <= 0)
38
- return "";
39
- if (width === 1)
40
- return left;
41
- return `${left}${inner}${right}`;
42
- }
43
- function normalizeStatusText(statusText) {
44
- const trimmed = statusText.trim();
45
- return trimmed.length > 0 ? ` ${trimmed} ` : " ";
46
- }
47
- function buildPromptBoxFrame(width, statusText, middleRowCount) {
48
- const safeWidth = Math.max(0, width);
49
- const innerWidth = Math.max(0, safeWidth - 2);
50
- const rowCount = Math.max(1, middleRowCount);
51
- const middleInner = " ".repeat(innerWidth);
52
- const normalizedStatus = normalizeStatusText(statusText);
53
- // Keep the right edge on narrow terminals so model suffixes remain visible.
54
- const clippedStatus = takeRightChars(normalizedStatus, innerWidth);
55
- const clippedStatusWidth = charLength(clippedStatus);
56
- const fill = "─".repeat(Math.max(0, innerWidth - clippedStatusWidth));
57
- const bottomInner = `${fill}${clippedStatus}`;
58
- return {
59
- top: buildBorderLine(safeWidth, "╭", "╮", "─"),
60
- middleRows: Array.from({ length: rowCount }, () => buildContentLine(safeWidth, "│", "│", middleInner)),
61
- bottom: buildContentLine(safeWidth, "╰", "╯", bottomInner),
62
- innerWidth
63
- };
64
- }
65
- function buildPromptBoxLayout(width, statusText, promptText = DEFAULT_PROMPT) {
66
- const frame = buildPromptBoxFrame(width, statusText, 1);
67
- const prompt = takeLeftChars(promptText, frame.innerWidth);
68
- const promptPadding = " ".repeat(Math.max(0, frame.innerWidth - charLength(prompt)));
69
- const middleInner = `${prompt}${promptPadding}`;
70
- return {
71
- top: frame.top,
72
- middle: buildContentLine(Math.max(0, width), "│", "│", middleInner),
73
- bottom: frame.bottom,
74
- innerWidth: frame.innerWidth,
75
- prompt,
76
- promptPadding
77
- };
78
- }