localpi 0.1.0 → 0.2.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/README.md +2 -2
- package/dist/src/localpi/catalog.js +38 -3
- package/dist/src/localpi/llama-server.js +39 -2
- package/dist/src/localpi/managed-runtime.js +12 -3
- package/dist/src/localpi/runtime-connection.js +2 -2
- package/dist/src/localpi/runtime-selection.js +71 -5
- package/dist/src/localpi/runtime.js +2 -2
- package/docs/runtime-specification.md +3 -2
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Localpi is a local Pi launcher for open-weight models.
|
|
|
4
4
|
|
|
5
5
|
By default, Localpi discovers available local providers, lets you choose when more than one model is loaded, points Pi at the selected model, and writes Pi config for the other discovered models so `/model` can switch among them during the session.
|
|
6
6
|
|
|
7
|
-
Localpi supports LM Studio, vLLM, custom OpenAI-compatible servers, and
|
|
7
|
+
Localpi supports LM Studio, vLLM, custom OpenAI-compatible servers, and an optional managed `llama-server` fallback.
|
|
8
8
|
|
|
9
9
|
Localpi is intentionally generic. It does not contain classifier prompts, dataset workflows, GitHub routing logic, or final-schema output machinery. Structured classifier runs belong in caller tools such as `localpager-agent`.
|
|
10
10
|
|
|
@@ -38,7 +38,7 @@ Target default:
|
|
|
38
38
|
localpi --model gemma-12b
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
This uses the default `auto` runtime. If exactly one model is loaded locally, Localpi selects it. If multiple models are loaded in an interactive terminal, Localpi boots Pi with a temporary default and opens Pi's native model selector. If no external model is loaded, Localpi can fall back to the managed `llama-server` default. Thinking starts as `off` unless `--thinking` or `LOCALPI_THINKING` sets another startup level.
|
|
41
|
+
This uses the default `auto` runtime. If exactly one model is loaded locally, Localpi selects it. If multiple models are loaded in an interactive terminal, Localpi boots Pi with a temporary default and opens Pi's native model selector. If no external model is loaded and `llama-server` is installed, Localpi can fall back to the managed `llama-server` default. Thinking starts as `off` unless `--thinking` or `LOCALPI_THINKING` sets another startup level.
|
|
42
42
|
|
|
43
43
|
LM Studio is explicit:
|
|
44
44
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { listModels } from "../llm/openai.js";
|
|
2
|
-
import { getManagedLlamaServerMetadata, getLlamaServerModels, llamaBaseUrl } from "./llama-server.js";
|
|
2
|
+
import { getManagedLlamaServerMetadata, getLlamaServerModels, llamaBaseUrl, managedLlamaServerUnavailableMessage } from "./llama-server.js";
|
|
3
3
|
import { listModelAliases, resolveLlamaModel } from "./models.js";
|
|
4
4
|
import { providerConfigs } from "./provider-registry.js";
|
|
5
5
|
export async function discoverModelCatalog(options) {
|
|
@@ -40,7 +40,14 @@ async function discoverOpenAiCompatibleProvider(config, options) {
|
|
|
40
40
|
if (explicitOpenAiProviderSelected(options, config.id)) {
|
|
41
41
|
throw error;
|
|
42
42
|
}
|
|
43
|
-
return {
|
|
43
|
+
return {
|
|
44
|
+
models: [],
|
|
45
|
+
warnings: [
|
|
46
|
+
catalogWarning(config.id, config.name, "provider-not-responding", {
|
|
47
|
+
message: `not responding at ${config.baseUrl}`
|
|
48
|
+
})
|
|
49
|
+
]
|
|
50
|
+
};
|
|
44
51
|
}
|
|
45
52
|
}
|
|
46
53
|
function openAiCatalogModel(config, model, options) {
|
|
@@ -81,10 +88,18 @@ async function discoverManagedLlamaProvider(config, options) {
|
|
|
81
88
|
const baseUrl = llamaBaseUrl(options);
|
|
82
89
|
const aliases = await listModelAliases();
|
|
83
90
|
const loaded = await loadedLlamaModels(config, options, baseUrl, aliases);
|
|
91
|
+
const unavailableMessage = await managedLlamaServerUnavailableMessage(options);
|
|
84
92
|
const startable = await startableLlamaModels(config, options, baseUrl, loaded.models, aliases);
|
|
85
93
|
return {
|
|
86
94
|
models: [...loaded.models, ...startable],
|
|
87
|
-
warnings:
|
|
95
|
+
warnings: unavailableMessage === undefined
|
|
96
|
+
? loaded.warnings
|
|
97
|
+
: [
|
|
98
|
+
...loaded.warnings,
|
|
99
|
+
catalogWarning(config.id, config.name, "managed-command-unavailable", {
|
|
100
|
+
message: unavailableMessage
|
|
101
|
+
})
|
|
102
|
+
]
|
|
88
103
|
};
|
|
89
104
|
}
|
|
90
105
|
async function loadedLlamaModels(config, options, baseUrl, aliases) {
|
|
@@ -162,6 +177,26 @@ export function managedModelSupportsReasoning(modelId) {
|
|
|
162
177
|
normalized.includes("gpt-oss") ||
|
|
163
178
|
normalized.includes("gemma-4"));
|
|
164
179
|
}
|
|
180
|
+
export function runtimeCatalogWarning(providerId, providerName, message) {
|
|
181
|
+
return catalogWarning(providerId, providerName, "runtime-warning", { message });
|
|
182
|
+
}
|
|
183
|
+
export function formatCatalogWarning(warning) {
|
|
184
|
+
switch (warning.code) {
|
|
185
|
+
case "provider-not-responding":
|
|
186
|
+
return `${warning.providerName} is ${warning.message}`;
|
|
187
|
+
case "managed-command-unavailable":
|
|
188
|
+
case "runtime-warning":
|
|
189
|
+
return warning.message;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function catalogWarning(providerId, providerName, code, options) {
|
|
193
|
+
return {
|
|
194
|
+
providerId,
|
|
195
|
+
providerName,
|
|
196
|
+
code,
|
|
197
|
+
message: options.message
|
|
198
|
+
};
|
|
199
|
+
}
|
|
165
200
|
function isDeepSeekThinkingModel(normalizedModelId) {
|
|
166
201
|
return (normalizedModelId.includes("deepseek") &&
|
|
167
202
|
(hasModelToken(normalizedModelId, "r1") ||
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFile, spawn } from "node:child_process";
|
|
2
|
-
import { closeSync, openSync } from "node:fs";
|
|
3
|
-
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { closeSync, constants, openSync } from "node:fs";
|
|
3
|
+
import { access, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { listModels, normalizeBaseUrl } from "../llm/openai.js";
|
|
6
6
|
export async function ensureLlamaServer(options, model) {
|
|
@@ -23,6 +23,12 @@ export async function ensureLlamaServer(options, model) {
|
|
|
23
23
|
contextWindow: requestedContextWindow(options, model)
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
+
export async function managedLlamaServerUnavailableMessage(options) {
|
|
27
|
+
if (await executableExists(options.serverCommand)) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
return `llama-server command ${options.serverCommand} is not available; managed llama-server fallback disabled`;
|
|
31
|
+
}
|
|
26
32
|
async function handleExistingServer(options, model, baseUrl, warnings) {
|
|
27
33
|
const state = await existingServerState(options, baseUrl);
|
|
28
34
|
if (state.existing === undefined) {
|
|
@@ -118,6 +124,7 @@ function rejectExternalModelConflict(baseUrl, existing, requestedModel) {
|
|
|
118
124
|
throw new Error(`server at ${baseUrl} is already serving ${ids.join(", ")}; stop it or choose that model before starting ${requestedModel}`);
|
|
119
125
|
}
|
|
120
126
|
async function startManagedServer(options, model) {
|
|
127
|
+
await assertManagedLlamaServerAvailable(options);
|
|
121
128
|
await mkdir(serverDir(options), { recursive: true });
|
|
122
129
|
const logPath = path.join(serverDir(options), "llama-server.log");
|
|
123
130
|
const logFd = openSync(logPath, "a");
|
|
@@ -158,6 +165,36 @@ async function startManagedServer(options, model) {
|
|
|
158
165
|
closeSync(logFd);
|
|
159
166
|
}
|
|
160
167
|
}
|
|
168
|
+
async function assertManagedLlamaServerAvailable(options) {
|
|
169
|
+
if (await executableExists(options.serverCommand)) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
throw new Error(`failed to start llama-server: executable not found: ${options.serverCommand}; install llama-server or pass --server-command`);
|
|
173
|
+
}
|
|
174
|
+
async function executableExists(command) {
|
|
175
|
+
if (command.length === 0) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
if (command.includes("/") || command.includes("\\")) {
|
|
179
|
+
return canExecute(command);
|
|
180
|
+
}
|
|
181
|
+
const entries = (process.env["PATH"] ?? "").split(path.delimiter).filter((entry) => entry !== "");
|
|
182
|
+
for (const entry of entries) {
|
|
183
|
+
if (await canExecute(path.join(entry, command))) {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
async function canExecute(filePath) {
|
|
190
|
+
try {
|
|
191
|
+
await access(filePath, constants.X_OK);
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
161
198
|
function serverArgs(options, model) {
|
|
162
199
|
const endpoint = managedEndpoint(options);
|
|
163
200
|
return [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ensureLlamaServer, getManagedLlamaServerMetadata, getLlamaServerModels, llamaBaseUrl, managedLlamaServerNeedsRestart, stopManagedLlamaServer } from "./llama-server.js";
|
|
2
|
-
import { managedModelSupportsReasoning } from "./catalog.js";
|
|
2
|
+
import { managedModelSupportsReasoning, runtimeCatalogWarning } from "./catalog.js";
|
|
3
3
|
import { catalogModelFromModelInfo, catalogRuntimeConnection, connectionCatalogModels, modelChoiceList, optionalContextWindow, replaceManagedLoadedModels } from "./runtime-connection.js";
|
|
4
4
|
import { defaultLlamaModelName, findModelAlias, resolveLlamaModel } from "./models.js";
|
|
5
5
|
export async function resolveLlamaRuntime(options) {
|
|
@@ -32,7 +32,10 @@ export async function resolveSelectedLlamaRuntime(options, selected, catalog) {
|
|
|
32
32
|
const selectedModel = managedCatalogModelFromConnection(options, selected, existing);
|
|
33
33
|
return catalogRuntimeConnection(options, selectedModel, {
|
|
34
34
|
models: replaceManagedLoadedModels(catalog.models, selected, existing.catalogModels),
|
|
35
|
-
warnings: [
|
|
35
|
+
warnings: [
|
|
36
|
+
...catalog.warnings,
|
|
37
|
+
...runtimeWarnings("llama-server", "llama-server", existing.warnings)
|
|
38
|
+
]
|
|
36
39
|
});
|
|
37
40
|
}
|
|
38
41
|
return startSelectedLlamaRuntime(options, selected, catalog);
|
|
@@ -175,7 +178,10 @@ async function startSelectedLlamaRuntime(options, selected, catalog) {
|
|
|
175
178
|
const loadedSelected = runtimeSelectedCatalogModel(options, selected, runtime);
|
|
176
179
|
return catalogRuntimeConnection(options, loadedSelected, {
|
|
177
180
|
models: replaceManagedLoadedModels(catalog.models, selected, [loadedSelected]),
|
|
178
|
-
warnings: [
|
|
181
|
+
warnings: [
|
|
182
|
+
...catalog.warnings,
|
|
183
|
+
...runtimeWarnings("llama-server", "llama-server", runtime.warnings)
|
|
184
|
+
]
|
|
179
185
|
});
|
|
180
186
|
}
|
|
181
187
|
async function assertDirectLlamaStartIsSafe(options, model) {
|
|
@@ -217,6 +223,9 @@ function assertNoLoadedExternalModels(catalog) {
|
|
|
217
223
|
}
|
|
218
224
|
throw new Error(`external local models are already loaded; choose one or unload them before starting llama-server:\n${modelChoiceList(external)}`);
|
|
219
225
|
}
|
|
226
|
+
function runtimeWarnings(providerId, providerName, warnings) {
|
|
227
|
+
return warnings.map((warning) => runtimeCatalogWarning(providerId, providerName, warning));
|
|
228
|
+
}
|
|
220
229
|
function isGgufPathRequest(value) {
|
|
221
230
|
return value.endsWith(".gguf") || value.includes("/") || value.includes("\\");
|
|
222
231
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { managedModelSupportsReasoning } from "./catalog.js";
|
|
1
|
+
import { formatCatalogWarning, managedModelSupportsReasoning } from "./catalog.js";
|
|
2
2
|
export function connectionStatus(connection) {
|
|
3
3
|
return ([
|
|
4
4
|
`runtime: ${connection.runtime}`,
|
|
@@ -25,7 +25,7 @@ export function catalogRuntimeConnection(options, selected, catalog) {
|
|
|
25
25
|
model: selected.modelId,
|
|
26
26
|
availableModels: providerModels.map((model) => model.modelId),
|
|
27
27
|
catalogModels: catalog.models.filter((model) => model.availability === "loaded"),
|
|
28
|
-
warnings: catalog.warnings,
|
|
28
|
+
warnings: catalog.warnings.map(formatCatalogWarning),
|
|
29
29
|
...optionalContextWindow(options.contextWindow ?? selected.contextWindow)
|
|
30
30
|
};
|
|
31
31
|
}
|
|
@@ -16,7 +16,7 @@ export async function selectCatalogModel(options, catalog) {
|
|
|
16
16
|
if (selection.model !== "auto") {
|
|
17
17
|
return selectExplicitCatalogModel(options, providerFiltered, selection.provider, selection.model);
|
|
18
18
|
}
|
|
19
|
-
return selectAutomaticCatalogModel(providerFiltered, catalog.warnings);
|
|
19
|
+
return selectAutomaticCatalogModel(providerFiltered, warningsForProvider(catalog.warnings, selection.provider));
|
|
20
20
|
}
|
|
21
21
|
async function selectExplicitCatalogModel(options, models, provider, requested) {
|
|
22
22
|
const matches = matchingCatalogModels(models, requested);
|
|
@@ -39,11 +39,11 @@ function selectAutomaticCatalogModel(models, warnings) {
|
|
|
39
39
|
if (onlyLoaded !== undefined) {
|
|
40
40
|
return onlyLoaded;
|
|
41
41
|
}
|
|
42
|
-
const fallback = startableFallback(models);
|
|
42
|
+
const fallback = startableFallback(models, warnings);
|
|
43
43
|
if (fallback !== undefined) {
|
|
44
44
|
return fallback;
|
|
45
45
|
}
|
|
46
|
-
throw new Error(
|
|
46
|
+
throw new Error(noLoadedModelsMessage(models, warnings));
|
|
47
47
|
}
|
|
48
48
|
function modelsForProvider(models, provider) {
|
|
49
49
|
return provider === undefined ? models : models.filter((model) => model.providerId === provider);
|
|
@@ -69,7 +69,73 @@ function matchingCatalogModels(models, requested) {
|
|
|
69
69
|
function isGgufFilePathRequest(value) {
|
|
70
70
|
return value.toLowerCase().endsWith(".gguf") || value.includes("\\");
|
|
71
71
|
}
|
|
72
|
-
function startableFallback(models) {
|
|
73
|
-
const startable = models.filter((model) => model.availability === "startable"
|
|
72
|
+
function startableFallback(models, warnings) {
|
|
73
|
+
const startable = models.filter((model) => model.availability === "startable" &&
|
|
74
|
+
(model.runtime !== "managed-llama-server" || managedLlamaFallbackAvailable(warnings)));
|
|
74
75
|
return (startable.find((model) => model.aliases.includes(defaultLlamaModelName()) || model.modelId === defaultLlamaModelName()) ?? startable[0]);
|
|
75
76
|
}
|
|
77
|
+
function managedLlamaFallbackAvailable(warnings) {
|
|
78
|
+
return !warnings.some((warning) => warning.providerId === "llama-server" && warning.code === "managed-command-unavailable");
|
|
79
|
+
}
|
|
80
|
+
function noLoadedModelsMessage(models, warnings) {
|
|
81
|
+
const sections = engineSections(models, warnings);
|
|
82
|
+
if (sections.length === 0) {
|
|
83
|
+
return "no loaded models available\n\nTried engines:\n\n- none reported usable models";
|
|
84
|
+
}
|
|
85
|
+
return [
|
|
86
|
+
"no loaded models available",
|
|
87
|
+
"",
|
|
88
|
+
"Tried engines:",
|
|
89
|
+
"",
|
|
90
|
+
sections.map(formatEngineSection).join("\n\n")
|
|
91
|
+
].join("\n");
|
|
92
|
+
}
|
|
93
|
+
function engineSections(models, warnings) {
|
|
94
|
+
const sections = new Map();
|
|
95
|
+
for (const model of models) {
|
|
96
|
+
const section = ensureEngineSection(sections, model.providerId, model.providerName);
|
|
97
|
+
const entry = `${model.providerId}/${model.modelId}`;
|
|
98
|
+
const updated = model.availability === "loaded"
|
|
99
|
+
? { ...section, loaded: [...section.loaded, entry] }
|
|
100
|
+
: { ...section, startable: [...section.startable, entry] };
|
|
101
|
+
sections.set(model.providerId, updated);
|
|
102
|
+
}
|
|
103
|
+
for (const warning of warnings) {
|
|
104
|
+
const section = ensureEngineSection(sections, warning.providerId, warning.providerName);
|
|
105
|
+
sections.set(warning.providerId, {
|
|
106
|
+
...section,
|
|
107
|
+
warnings: [...section.warnings, warning.message]
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return [...sections.values()];
|
|
111
|
+
}
|
|
112
|
+
function ensureEngineSection(sections, key, title) {
|
|
113
|
+
const existing = sections.get(key);
|
|
114
|
+
if (existing !== undefined) {
|
|
115
|
+
return existing;
|
|
116
|
+
}
|
|
117
|
+
const section = { title, loaded: [], startable: [], warnings: [] };
|
|
118
|
+
sections.set(key, section);
|
|
119
|
+
return section;
|
|
120
|
+
}
|
|
121
|
+
function formatEngineSection(section) {
|
|
122
|
+
const lines = [`${section.title}:`];
|
|
123
|
+
if (section.loaded.length === 0) {
|
|
124
|
+
lines.push("- loaded models: none");
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
lines.push(`- loaded models: ${section.loaded.join(", ")}`);
|
|
128
|
+
}
|
|
129
|
+
if (section.startable.length > 0) {
|
|
130
|
+
lines.push(`- startable models: ${section.startable.join(", ")}`);
|
|
131
|
+
}
|
|
132
|
+
for (const warning of section.warnings) {
|
|
133
|
+
lines.push(`- ${warning}`);
|
|
134
|
+
}
|
|
135
|
+
return lines.join("\n");
|
|
136
|
+
}
|
|
137
|
+
function warningsForProvider(warnings, provider) {
|
|
138
|
+
return provider === undefined
|
|
139
|
+
? warnings
|
|
140
|
+
: warnings.filter((warning) => warning.providerId === provider);
|
|
141
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { discoverModelCatalog } from "./catalog.js";
|
|
1
|
+
import { discoverModelCatalog, formatCatalogWarning } from "./catalog.js";
|
|
2
2
|
import { llamaBaseUrl, llamaServerStatus, stopManagedLlamaServer } from "./llama-server.js";
|
|
3
3
|
import { listModelAliases } from "./models.js";
|
|
4
4
|
import { catalogRuntimeConnection, connectionStatus, statusModelList } from "./runtime-connection.js";
|
|
@@ -75,7 +75,7 @@ async function catalogStatusOutput(options) {
|
|
|
75
75
|
`runtime: ${options.runtime}`,
|
|
76
76
|
`loaded models: ${statusModelList(loaded)}`,
|
|
77
77
|
`startable models: ${statusModelList(startable)}`,
|
|
78
|
-
...catalog.warnings.map((warning) => `warning: ${warning}`)
|
|
78
|
+
...catalog.warnings.map((warning) => `warning: ${formatCatalogWarning(warning)}`)
|
|
79
79
|
].join("\n") + "\n");
|
|
80
80
|
}
|
|
81
81
|
function requiredOpenAiBaseUrl(options) {
|
|
@@ -9,7 +9,7 @@ It should make the common local-model path one command while keeping the selecte
|
|
|
9
9
|
- Run Pi against local open-weight models without hand-editing Pi config.
|
|
10
10
|
- Discover local providers by default and select from the loaded model catalog.
|
|
11
11
|
- Support LM Studio and vLLM as built-in OpenAI-compatible providers.
|
|
12
|
-
- Keep managed `llama-server` as
|
|
12
|
+
- Keep managed `llama-server` as an optional fallback when no external model is loaded.
|
|
13
13
|
- Keep the tool generic: no classifier prompts, topic schemas, dataset generation, or final-schema output.
|
|
14
14
|
- Keep large model memory usage predictable by managing only one localpi-owned `llama-server` process at a time.
|
|
15
15
|
|
|
@@ -23,11 +23,12 @@ Localpi:
|
|
|
23
23
|
|
|
24
24
|
- probes built-in LM Studio and vLLM endpoints
|
|
25
25
|
- loads configured OpenAI-compatible providers from `--providers-file`, `LOCALPI_PROVIDERS_FILE`, or `LOCALPI_MODELS_FILE`
|
|
26
|
-
- includes the localpi-owned `llama-server` catalog as startable fallback entries
|
|
26
|
+
- includes the localpi-owned `llama-server` catalog as startable fallback entries when available
|
|
27
27
|
- selects the only loaded model automatically
|
|
28
28
|
- opens Pi's native model selector when multiple loaded models are available in an interactive TTY
|
|
29
29
|
- never prompts in non-interactive runs; automation can pin a model with concrete `--provider` and `--model` values
|
|
30
30
|
- treats `--provider` without `--model` as catalog scoping, not as a concrete model choice
|
|
31
|
+
- skips automatic managed `llama-server` fallback when the configured `llama-server` command is unavailable
|
|
31
32
|
- writes Pi config for all launch-time loaded catalog entries so Pi `/model` can switch among them
|
|
32
33
|
|
|
33
34
|
### `llama-server`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "localpi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Pi-compatible local model launcher with managed llama-server support.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
"url": "https://github.com/dutifuldev/localpi/issues"
|
|
13
13
|
},
|
|
14
14
|
"homepage": "https://github.com/dutifuldev/localpi#readme",
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
15
18
|
"bin": {
|
|
16
19
|
"localpi": "dist/src/cli/main.js"
|
|
17
20
|
},
|
|
@@ -45,7 +48,7 @@
|
|
|
45
48
|
"@vitest/coverage-v8": "^3.0.0",
|
|
46
49
|
"eslint": "^9.0.0",
|
|
47
50
|
"prettier": "^3.0.0",
|
|
48
|
-
"slophammer-ts": "
|
|
51
|
+
"slophammer-ts": "0.4.0",
|
|
49
52
|
"tsx": "^4.19.4",
|
|
50
53
|
"typescript": "^5.0.0",
|
|
51
54
|
"typescript-eslint": "^8.0.0",
|