@sheepbun/yips 0.1.1

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 (99) hide show
  1. package/dist/agent/commands/command-catalog.js +243 -0
  2. package/dist/agent/commands/commands.js +418 -0
  3. package/dist/agent/conductor.js +118 -0
  4. package/dist/agent/context/code-context.js +68 -0
  5. package/dist/agent/context/memory-store.js +159 -0
  6. package/dist/agent/context/session-store.js +211 -0
  7. package/dist/agent/protocol/tool-protocol.js +160 -0
  8. package/dist/agent/skills/skills.js +327 -0
  9. package/dist/agent/tools/tool-executor.js +415 -0
  10. package/dist/agent/tools/tool-safety.js +52 -0
  11. package/dist/app/index.js +35 -0
  12. package/dist/app/repl.js +105 -0
  13. package/dist/app/update-check.js +132 -0
  14. package/dist/app/version.js +51 -0
  15. package/dist/code-context.js +68 -0
  16. package/dist/colors.js +204 -0
  17. package/dist/command-catalog.js +242 -0
  18. package/dist/commands.js +350 -0
  19. package/dist/conductor.js +94 -0
  20. package/dist/config/config.js +335 -0
  21. package/dist/config/hooks.js +187 -0
  22. package/dist/config.js +335 -0
  23. package/dist/downloader-state.js +302 -0
  24. package/dist/downloader-ui.js +289 -0
  25. package/dist/gateway/adapters/discord.js +108 -0
  26. package/dist/gateway/adapters/formatting.js +96 -0
  27. package/dist/gateway/adapters/telegram.js +106 -0
  28. package/dist/gateway/adapters/types.js +2 -0
  29. package/dist/gateway/adapters/whatsapp.js +124 -0
  30. package/dist/gateway/auth-policy.js +66 -0
  31. package/dist/gateway/core.js +87 -0
  32. package/dist/gateway/headless-conductor.js +328 -0
  33. package/dist/gateway/message-router.js +23 -0
  34. package/dist/gateway/rate-limiter.js +48 -0
  35. package/dist/gateway/runtime/backend-policy.js +18 -0
  36. package/dist/gateway/runtime/discord-bot.js +104 -0
  37. package/dist/gateway/runtime/discord-main.js +69 -0
  38. package/dist/gateway/session-manager.js +77 -0
  39. package/dist/gateway/types.js +2 -0
  40. package/dist/hardware.js +92 -0
  41. package/dist/hooks.js +187 -0
  42. package/dist/index.js +34 -0
  43. package/dist/input-engine.js +250 -0
  44. package/dist/llama-client.js +227 -0
  45. package/dist/llama-server.js +620 -0
  46. package/dist/llm/llama-client.js +227 -0
  47. package/dist/llm/llama-server.js +620 -0
  48. package/dist/llm/token-counter.js +47 -0
  49. package/dist/memory-store.js +159 -0
  50. package/dist/messages.js +59 -0
  51. package/dist/model-downloader.js +382 -0
  52. package/dist/model-manager-state.js +118 -0
  53. package/dist/model-manager-ui.js +194 -0
  54. package/dist/model-manager.js +190 -0
  55. package/dist/models/hardware.js +92 -0
  56. package/dist/models/model-downloader.js +382 -0
  57. package/dist/models/model-manager.js +190 -0
  58. package/dist/prompt-box.js +78 -0
  59. package/dist/prompt-composer.js +498 -0
  60. package/dist/repl.js +105 -0
  61. package/dist/session-store.js +211 -0
  62. package/dist/spinner.js +76 -0
  63. package/dist/title-box.js +388 -0
  64. package/dist/token-counter.js +47 -0
  65. package/dist/tool-executor.js +415 -0
  66. package/dist/tool-protocol.js +121 -0
  67. package/dist/tool-safety.js +52 -0
  68. package/dist/tui/app.js +2553 -0
  69. package/dist/tui/startup.js +56 -0
  70. package/dist/tui-input-routing.js +53 -0
  71. package/dist/tui.js +51 -0
  72. package/dist/types/app-types.js +2 -0
  73. package/dist/types.js +2 -0
  74. package/dist/ui/colors.js +204 -0
  75. package/dist/ui/downloader/downloader-state.js +302 -0
  76. package/dist/ui/downloader/downloader-ui.js +289 -0
  77. package/dist/ui/input/input-engine.js +250 -0
  78. package/dist/ui/input/tui-input-routing.js +53 -0
  79. package/dist/ui/input/vt-session.js +168 -0
  80. package/dist/ui/messages.js +59 -0
  81. package/dist/ui/model-manager/model-manager-state.js +118 -0
  82. package/dist/ui/model-manager/model-manager-ui.js +194 -0
  83. package/dist/ui/prompt/prompt-box.js +78 -0
  84. package/dist/ui/prompt/prompt-composer.js +498 -0
  85. package/dist/ui/spinner.js +76 -0
  86. package/dist/ui/title-box.js +388 -0
  87. package/dist/ui/tui/app.js +6 -0
  88. package/dist/ui/tui/autocomplete.js +85 -0
  89. package/dist/ui/tui/constants.js +18 -0
  90. package/dist/ui/tui/history.js +29 -0
  91. package/dist/ui/tui/layout.js +341 -0
  92. package/dist/ui/tui/runtime-core.js +2584 -0
  93. package/dist/ui/tui/runtime-utils.js +53 -0
  94. package/dist/ui/tui/start-tui.js +54 -0
  95. package/dist/ui/tui/startup.js +56 -0
  96. package/dist/version.js +51 -0
  97. package/dist/vt-session.js +168 -0
  98. package/install.sh +457 -0
  99. package/package.json +128 -0
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createModelManagerState = createModelManagerState;
4
+ exports.setModelManagerLoading = setModelManagerLoading;
5
+ exports.setModelManagerError = setModelManagerError;
6
+ exports.setModelManagerModels = setModelManagerModels;
7
+ exports.setModelManagerSearchQuery = setModelManagerSearchQuery;
8
+ exports.moveModelManagerSelection = moveModelManagerSelection;
9
+ exports.removeModelById = removeModelById;
10
+ exports.getSelectedModel = getSelectedModel;
11
+ const model_manager_1 = require("./model-manager");
12
+ function createModelManagerState(memory) {
13
+ return {
14
+ isOpen: true,
15
+ searchQuery: "",
16
+ allModels: [],
17
+ models: [],
18
+ selectedModelIndex: 0,
19
+ scrollOffset: 0,
20
+ phase: "idle",
21
+ loading: false,
22
+ loadingMessage: "Loading models...",
23
+ errorMessage: "",
24
+ ramGb: memory.ramGb,
25
+ vramGb: memory.vramGb,
26
+ totalMemoryGb: memory.totalMemoryGb
27
+ };
28
+ }
29
+ function clampSelection(state) {
30
+ const max = Math.max(0, state.models.length - 1);
31
+ const selectedModelIndex = Math.min(Math.max(0, state.selectedModelIndex), max);
32
+ const scrollOffset = Math.min(Math.max(0, state.scrollOffset), Math.max(0, max));
33
+ return {
34
+ ...state,
35
+ selectedModelIndex,
36
+ scrollOffset
37
+ };
38
+ }
39
+ function setModelManagerLoading(state, message) {
40
+ return {
41
+ ...state,
42
+ phase: "loading",
43
+ loading: true,
44
+ loadingMessage: message,
45
+ errorMessage: ""
46
+ };
47
+ }
48
+ function setModelManagerError(state, message) {
49
+ return {
50
+ ...state,
51
+ phase: "error",
52
+ loading: false,
53
+ errorMessage: message
54
+ };
55
+ }
56
+ function setModelManagerModels(state, models) {
57
+ const filtered = (0, model_manager_1.filterModels)(models, state.searchQuery);
58
+ return clampSelection({
59
+ ...state,
60
+ allModels: models,
61
+ models: filtered,
62
+ phase: "idle",
63
+ loading: false,
64
+ errorMessage: "",
65
+ selectedModelIndex: filtered.length > 0 ? Math.min(state.selectedModelIndex, filtered.length - 1) : 0,
66
+ scrollOffset: 0
67
+ });
68
+ }
69
+ function setModelManagerSearchQuery(state, searchQuery) {
70
+ const models = (0, model_manager_1.filterModels)(state.allModels, searchQuery);
71
+ return clampSelection({
72
+ ...state,
73
+ searchQuery,
74
+ models,
75
+ selectedModelIndex: models.length > 0 ? Math.min(state.selectedModelIndex, models.length - 1) : 0,
76
+ scrollOffset: 0,
77
+ phase: state.phase === "error" ? "idle" : state.phase,
78
+ errorMessage: state.phase === "error" ? "" : state.errorMessage
79
+ });
80
+ }
81
+ function moveModelManagerSelection(state, delta, viewportSize) {
82
+ if (state.models.length === 0) {
83
+ return state;
84
+ }
85
+ const max = state.models.length - 1;
86
+ const nextIndex = Math.min(max, Math.max(0, state.selectedModelIndex + delta));
87
+ let nextScroll = state.scrollOffset;
88
+ if (nextIndex < nextScroll) {
89
+ nextScroll = nextIndex;
90
+ }
91
+ else if (nextIndex >= nextScroll + viewportSize) {
92
+ nextScroll = nextIndex - (viewportSize - 1);
93
+ }
94
+ return {
95
+ ...state,
96
+ selectedModelIndex: nextIndex,
97
+ scrollOffset: Math.max(0, nextScroll)
98
+ };
99
+ }
100
+ function removeModelById(state, id) {
101
+ const allModels = state.allModels.filter((model) => model.id !== id);
102
+ const models = (0, model_manager_1.filterModels)(allModels, state.searchQuery);
103
+ return clampSelection({
104
+ ...state,
105
+ allModels,
106
+ models,
107
+ selectedModelIndex: models.length > 0 ? Math.min(state.selectedModelIndex, models.length - 1) : 0,
108
+ phase: "idle",
109
+ loading: false,
110
+ errorMessage: ""
111
+ });
112
+ }
113
+ function getSelectedModel(state) {
114
+ if (state.models.length === 0) {
115
+ return null;
116
+ }
117
+ return state.models[state.selectedModelIndex] ?? null;
118
+ }
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderModelManagerLines = renderModelManagerLines;
4
+ const colors_1 = require("./colors");
5
+ const TAB_INACTIVE = { r: 0x88, g: 0x88, b: 0x88 };
6
+ const BLACK = { r: 0x00, g: 0x00, b: 0x00 };
7
+ const CURRENT_MODEL_BLUE = { r: 0x89, g: 0xcf, b: 0xf0 };
8
+ const FOCUS_ACCENT_BG = { r: 0xff, g: 0xcc, b: 0xff };
9
+ const MODEL_MANAGER_BODY_ROWS = 12;
10
+ const ANSI_BOLD_ON = "\u001b[1m";
11
+ const ANSI_BOLD_OFF = "\u001b[22m";
12
+ function charLength(text) {
13
+ return Array.from((0, colors_1.stripAnsi)(text)).length;
14
+ }
15
+ function fitLeft(text, width) {
16
+ const chars = Array.from(text);
17
+ if (chars.length <= width) {
18
+ return text;
19
+ }
20
+ if (width <= 3) {
21
+ return chars.slice(0, width).join("");
22
+ }
23
+ return `${chars.slice(0, width - 3).join("")}...`;
24
+ }
25
+ function fitRight(text, width) {
26
+ const chars = Array.from(text);
27
+ if (chars.length <= width) {
28
+ return text;
29
+ }
30
+ return chars.slice(chars.length - width).join("");
31
+ }
32
+ function makeBorderTop(width) {
33
+ if (width <= 1)
34
+ return "╭";
35
+ const prefix = "╭─── ";
36
+ const titleBrand = "Yips";
37
+ const titleDetail = " Model Manager";
38
+ const titleTail = " ";
39
+ const prefixLen = charLength(prefix);
40
+ const titleBrandLen = charLength(titleBrand);
41
+ const titleDetailLen = charLength(titleDetail);
42
+ const titleTailLen = charLength(titleTail);
43
+ const plainTitleLen = prefixLen + titleBrandLen + titleDetailLen + titleTailLen;
44
+ const fill = "─".repeat(Math.max(0, width - plainTitleLen - 1));
45
+ const fillOffset = prefixLen + titleBrandLen + titleDetailLen + titleTailLen;
46
+ const cornerOffset = width - 1;
47
+ return `${(0, colors_1.horizontalGradientAtOffset)(prefix, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW, 0, width)}${ANSI_BOLD_ON}${(0, colors_1.horizontalGradient)(titleBrand, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW)}${(0, colors_1.colorText)(titleDetail, colors_1.GRADIENT_BLUE)}${ANSI_BOLD_OFF}${(0, colors_1.horizontalGradientAtOffset)(titleTail, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW, prefixLen + titleBrandLen + titleDetailLen, width)}${(0, colors_1.horizontalGradientAtOffset)(fill, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW, fillOffset, width)}${(0, colors_1.horizontalGradientAtOffset)("╮", colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW, cornerOffset, width)}`;
48
+ }
49
+ function makeBorderBottom(width) {
50
+ if (width <= 1)
51
+ return "╰";
52
+ const mid = "─".repeat(Math.max(0, width - 2));
53
+ return `${(0, colors_1.colorText)("╰", colors_1.GRADIENT_PINK)}${(0, colors_1.horizontalGradient)(mid, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW)}${(0, colors_1.colorText)("╯", colors_1.GRADIENT_YELLOW)}`;
54
+ }
55
+ function lineWithBorders(inner, innerWidth) {
56
+ let fitted = inner;
57
+ if (charLength(fitted) > innerWidth) {
58
+ fitted = fitLeft((0, colors_1.stripAnsi)(inner), innerWidth);
59
+ }
60
+ const padding = " ".repeat(Math.max(0, innerWidth - charLength(fitted)));
61
+ return `${(0, colors_1.colorText)("│", colors_1.GRADIENT_PINK)}${fitted}${padding}${(0, colors_1.colorText)("│", colors_1.GRADIENT_YELLOW)}`;
62
+ }
63
+ function highlightedRow(row) {
64
+ const chars = Array.from(row);
65
+ if (chars.length === 0)
66
+ return row;
67
+ const first = (0, colors_1.bgColorText)(chars[0] ?? "", FOCUS_ACCENT_BG, BLACK);
68
+ const rest = chars.slice(1).join("");
69
+ if (rest.length === 0) {
70
+ return first;
71
+ }
72
+ return `${first}${(0, colors_1.horizontalGradientBackground)(rest, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW, BLACK)}`;
73
+ }
74
+ function toSizeCell(sizeGb) {
75
+ return `${sizeGb.toFixed(1)}G`;
76
+ }
77
+ function getFileName(modelId) {
78
+ const parts = modelId.split("/");
79
+ return parts[parts.length - 1] ?? modelId;
80
+ }
81
+ function buildModelRows(state, contentWidth, rowCount, currentModel) {
82
+ if (state.models.length === 0) {
83
+ return [(0, colors_1.colorText)("No models found in local models directory.", colors_1.WARNING_YELLOW)];
84
+ }
85
+ const markerWidth = 3;
86
+ const separators = 4 * 3;
87
+ const minBackendWidth = 9;
88
+ const minProviderWidth = 14;
89
+ const minNameWidth = 16;
90
+ const minFileWidth = 18;
91
+ const minSizeWidth = 7;
92
+ let backendWidth = minBackendWidth;
93
+ let providerWidth = minProviderWidth;
94
+ let nameWidth = minNameWidth;
95
+ let fileWidth = minFileWidth;
96
+ let sizeWidth = minSizeWidth;
97
+ const minimumTotalColumnWidth = minBackendWidth + minProviderWidth + minNameWidth + minFileWidth + minSizeWidth;
98
+ const availableTotalColumnWidth = Math.max(5, contentWidth - markerWidth - separators);
99
+ if (availableTotalColumnWidth >= minimumTotalColumnWidth) {
100
+ const extra = availableTotalColumnWidth - minimumTotalColumnWidth;
101
+ const growBy = Math.floor(extra / 5);
102
+ const remainder = extra % 5;
103
+ backendWidth += growBy + (remainder > 0 ? 1 : 0);
104
+ providerWidth += growBy + (remainder > 1 ? 1 : 0);
105
+ nameWidth += growBy + (remainder > 2 ? 1 : 0);
106
+ fileWidth += growBy + (remainder > 3 ? 1 : 0);
107
+ sizeWidth += growBy;
108
+ }
109
+ else {
110
+ const minAllowed = [4, 6, 8, 8, 4];
111
+ const widths = [
112
+ backendWidth,
113
+ providerWidth,
114
+ nameWidth,
115
+ fileWidth,
116
+ sizeWidth
117
+ ];
118
+ const reduceOrder = [3, 2, 1, 0, 4];
119
+ let deficit = minimumTotalColumnWidth - availableTotalColumnWidth;
120
+ while (deficit > 0) {
121
+ let reducedThisPass = false;
122
+ for (const index of reduceOrder) {
123
+ if (deficit <= 0) {
124
+ break;
125
+ }
126
+ if (widths[index] > minAllowed[index]) {
127
+ widths[index] -= 1;
128
+ deficit -= 1;
129
+ reducedThisPass = true;
130
+ }
131
+ }
132
+ if (!reducedThisPass) {
133
+ break;
134
+ }
135
+ }
136
+ [backendWidth, providerWidth, nameWidth, fileWidth, sizeWidth] = widths;
137
+ }
138
+ const rows = [];
139
+ const header = ` ${"Backend".padEnd(backendWidth, " ")} | ${"Provider".padEnd(providerWidth, " ")} | ${"Name".padEnd(nameWidth, " ")} | ${"File".padEnd(fileWidth, " ")} | ${"Size".padStart(sizeWidth, " ")}`;
140
+ rows.push((0, colors_1.horizontalGradient)(header, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW));
141
+ const visibleRowCount = Math.max(0, rowCount - 1);
142
+ const start = state.scrollOffset;
143
+ const end = Math.min(state.models.length, start + visibleRowCount);
144
+ for (let index = start; index < end; index++) {
145
+ const model = state.models[index];
146
+ if (!model)
147
+ continue;
148
+ const selected = index === state.selectedModelIndex;
149
+ const current = model.id === currentModel;
150
+ const prefix = `${selected ? ">" : " "}${current ? "*" : " "} `;
151
+ const backend = fitLeft(model.friendlyBackend, backendWidth).padEnd(backendWidth, " ");
152
+ const provider = fitLeft(model.host, providerWidth).padEnd(providerWidth, " ");
153
+ const name = fitLeft(model.friendlyName, nameWidth).padEnd(nameWidth, " ");
154
+ const file = fitLeft(getFileName(model.id), fileWidth).padEnd(fileWidth, " ");
155
+ const sizeCell = toSizeCell(model.sizeGb).padStart(sizeWidth, " ");
156
+ const row = `${prefix}${backend} | ${provider} | ${name} | ${file} | ${sizeCell}`;
157
+ if (selected) {
158
+ rows.push(highlightedRow(row));
159
+ continue;
160
+ }
161
+ rows.push(current ? (0, colors_1.colorText)(row, CURRENT_MODEL_BLUE) : row);
162
+ }
163
+ return rows;
164
+ }
165
+ function fillRows(rows, rowCount) {
166
+ if (rows.length >= rowCount) {
167
+ return rows.slice(0, rowCount);
168
+ }
169
+ return [...rows, ...new Array(rowCount - rows.length).fill("")];
170
+ }
171
+ function renderModelManagerLines(options) {
172
+ const width = Math.max(20, options.width);
173
+ const innerWidth = Math.max(1, width - 2);
174
+ const rows = [];
175
+ rows.push(makeBorderTop(width));
176
+ const leftLabel = (0, colors_1.colorText)(" Local ", TAB_INACTIVE);
177
+ const specText = `RAM: ${options.state.ramGb.toFixed(1)}GB | VRAM: ${options.state.vramGb.toFixed(1)}GB`;
178
+ const gap = Math.max(1, innerWidth - charLength(leftLabel) - charLength(specText));
179
+ rows.push(lineWithBorders(`${leftLabel}${" ".repeat(gap)}${fitRight(specText, Math.max(1, innerWidth - charLength(leftLabel) - 1))}`, innerWidth));
180
+ let bodyRows;
181
+ if (options.state.phase === "loading") {
182
+ bodyRows = fillRows([`Loading: ${options.state.loadingMessage}`], MODEL_MANAGER_BODY_ROWS);
183
+ }
184
+ else if (options.state.phase === "error" && options.state.errorMessage.length > 0) {
185
+ bodyRows = fillRows([`Error: ${options.state.errorMessage}`], MODEL_MANAGER_BODY_ROWS);
186
+ }
187
+ else {
188
+ bodyRows = fillRows(buildModelRows(options.state, innerWidth, MODEL_MANAGER_BODY_ROWS, options.currentModel), MODEL_MANAGER_BODY_ROWS);
189
+ }
190
+ rows.push(...bodyRows.map((row) => lineWithBorders(row, innerWidth)));
191
+ rows.push(lineWithBorders((0, colors_1.horizontalGradient)("[Enter] Select [↑/↓] Move [Del] Delete Local [T] Downloader [Esc] Close", colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW), innerWidth));
192
+ rows.push(makeBorderBottom(width));
193
+ return rows;
194
+ }
@@ -0,0 +1,190 @@
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("./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
+ }
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getSystemSpecs = getSystemSpecs;
4
+ exports.clearSystemSpecsCache = clearSystemSpecsCache;
5
+ const node_child_process_1 = require("node:child_process");
6
+ const node_fs_1 = require("node:fs");
7
+ const node_fs_2 = require("node:fs");
8
+ const node_os_1 = require("node:os");
9
+ const node_path_1 = require("node:path");
10
+ const node_fs_3 = require("node:fs");
11
+ function roundOne(value) {
12
+ return Math.round(value * 10) / 10;
13
+ }
14
+ function getRamGb() {
15
+ return roundOne((0, node_os_1.totalmem)() / (1024 ** 3));
16
+ }
17
+ function getNvidiaVramGb() {
18
+ try {
19
+ const result = (0, node_child_process_1.execFileSync)("nvidia-smi", ["--query-gpu=memory.total", "--format=csv,noheader,nounits"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
20
+ const totalMb = result
21
+ .split(/\r?\n/u)
22
+ .map((line) => line.trim())
23
+ .filter((line) => line.length > 0)
24
+ .map((line) => Number(line))
25
+ .filter((value) => Number.isFinite(value) && value > 0)
26
+ .reduce((sum, value) => sum + value, 0);
27
+ return totalMb > 0 ? roundOne(totalMb / 1024) : 0;
28
+ }
29
+ catch {
30
+ return 0;
31
+ }
32
+ }
33
+ function getAmdVramGb() {
34
+ try {
35
+ const paths = (0, node_fs_2.globSync)("/sys/class/drm/card*/device/mem_info_vram_total");
36
+ if (paths.length === 0) {
37
+ return 0;
38
+ }
39
+ const totalBytes = paths
40
+ .map((path) => (0, node_fs_1.readFileSync)(path, "utf8").trim())
41
+ .map((value) => Number(value))
42
+ .filter((value) => Number.isFinite(value) && value > 0)
43
+ .reduce((sum, value) => sum + value, 0);
44
+ return totalBytes > 0 ? roundOne(totalBytes / (1024 ** 3)) : 0;
45
+ }
46
+ catch {
47
+ return 0;
48
+ }
49
+ }
50
+ function getModelsDir() {
51
+ const override = process.env["YIPS_MODELS_DIR"]?.trim();
52
+ if (override && override.length > 0) {
53
+ return override;
54
+ }
55
+ return (0, node_path_1.join)((0, node_os_1.homedir)(), ".yips", "models");
56
+ }
57
+ function getDiskFreeGb(path) {
58
+ try {
59
+ const fsStats = (0, node_fs_3.statfsSync)(path);
60
+ const blockSize = Number(fsStats.bsize);
61
+ const available = Number(fsStats.bavail);
62
+ if (!Number.isFinite(blockSize) || !Number.isFinite(available) || blockSize <= 0 || available < 0) {
63
+ return 0;
64
+ }
65
+ return roundOne((blockSize * available) / (1024 ** 3));
66
+ }
67
+ catch {
68
+ return 0;
69
+ }
70
+ }
71
+ let cachedSpecs = null;
72
+ function getSystemSpecs() {
73
+ if (cachedSpecs) {
74
+ return cachedSpecs;
75
+ }
76
+ const ramGb = getRamGb();
77
+ const nvidiaVramGb = getNvidiaVramGb();
78
+ const amdVramGb = nvidiaVramGb > 0 ? 0 : getAmdVramGb();
79
+ const vramGb = nvidiaVramGb > 0 ? nvidiaVramGb : amdVramGb;
80
+ const gpuType = nvidiaVramGb > 0 ? "nvidia" : amdVramGb > 0 ? "amd" : "unknown";
81
+ cachedSpecs = {
82
+ ramGb,
83
+ vramGb,
84
+ totalMemoryGb: roundOne(ramGb + vramGb),
85
+ diskFreeGb: getDiskFreeGb(getModelsDir()),
86
+ gpuType
87
+ };
88
+ return cachedSpecs;
89
+ }
90
+ function clearSystemSpecsCache() {
91
+ cachedSpecs = null;
92
+ }