@index9/mcp 4.0.1 → 5.0.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/dist/cli.js +150 -31
- package/manifest.json +1 -1
- package/package.json +6 -5
package/dist/cli.js
CHANGED
|
@@ -109,19 +109,30 @@ Parameters:
|
|
|
109
109
|
- limit: Page size
|
|
110
110
|
- cursor: Opaque pagination cursor
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
Each result has: id, name, description, created (unix seconds), createdAt (ISO 8601), contextLength, maxOutputTokens, pricing.{promptPerMillion, completionPerMillion} (USD per million tokens, numbers), capabilities[], score.
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
Scores: 0-100, higher = more relevant. The best match in each page scores 100; others are scaled proportionally. Combines semantic similarity and keyword matching. Null when sorting by price or date.
|
|
115
|
+
|
|
116
|
+
Pass result.id to get_models for full specs or to test_model for live testing.`,
|
|
115
117
|
requiresKey: false
|
|
116
118
|
},
|
|
117
119
|
get_models: {
|
|
118
120
|
name: "get_models",
|
|
119
121
|
summary: "Get full model metadata by IDs or aliases (batch, up to 100)",
|
|
120
|
-
description: `Get
|
|
122
|
+
description: `Get full specs for one or more models by id or alias. Accepts up to ${LIMITS.getModelsMax} ids per call \u2014 use this for batch comparison.
|
|
123
|
+
|
|
124
|
+
Call after find_models to inspect candidates, or directly when the user names a model (format: 'provider/model-name').
|
|
121
125
|
|
|
122
|
-
|
|
126
|
+
Response: { results: (Model | null)[], missingIds: string[], resolvedAliases?: Record<alias, canonicalId>, ambiguousAliases?: Record<alias, candidateIds[]> }. Each non-null result has:
|
|
127
|
+
- id, canonicalSlug, name, description
|
|
128
|
+
- created (unix seconds), createdAt (ISO 8601), knowledgeCutoff (ISO date or null)
|
|
129
|
+
- contextLength (tokens), maxOutputTokens, isModerated
|
|
130
|
+
- pricing: { promptPerMillion, completionPerMillion, requestUsd, imageUsd } \u2014 all USD, all numbers. Token prices are per million tokens; request/image are per unit.
|
|
131
|
+
- architecture: { inputModalities[], outputModalities[], tokenizer, instructType }
|
|
132
|
+
- capabilities[]: normalized capability flags (same values as find_models and capabilitiesAll/Any)
|
|
133
|
+
- supportedParameters[]: OpenRouter parameters the model accepts (e.g., "temperature", "tools", "response_format")
|
|
123
134
|
|
|
124
|
-
|
|
135
|
+
Entries in results are null when the id is unknown; those ids appear in missingIds. Ambiguous aliases appear in ambiguousAliases with candidate canonical ids \u2014 pass a canonical id to disambiguate.`,
|
|
125
136
|
requiresKey: false
|
|
126
137
|
},
|
|
127
138
|
test_model: {
|
|
@@ -140,9 +151,9 @@ Parameters:
|
|
|
140
151
|
- prompt: Prompt text (required for dryRun; required for live unless userContent provided)
|
|
141
152
|
- dryRun: If true, return cost estimates only
|
|
142
153
|
- expectedCompletionTokens: Optional completion token estimate used by dryRun
|
|
143
|
-
-
|
|
154
|
+
- maxTokens, systemPrompt, temperature, topP, seed, responseFormat, enforceJson, retries: Live-testing controls (ignored when dryRun=true)
|
|
144
155
|
|
|
145
|
-
Use find_models or get_models first to identify model
|
|
156
|
+
Results (live): each result carries modelId (the id you passed), resolvedModelId (canonical id, present when the input was an alias), ok, response, latencyMs, tokens { prompt, completion }, cost (USD; live from OpenRouter when available, else estimated from cached pricing), and truncated=true when finish_reason is "length". Use find_models or get_models first to identify model ids.`,
|
|
146
157
|
requiresKey: true
|
|
147
158
|
}
|
|
148
159
|
};
|
|
@@ -150,15 +161,17 @@ var PARAM_DESCRIPTIONS = {
|
|
|
150
161
|
q: "Natural language search query describing desired model characteristics (e.g., 'fast cheap coding model'). Uses semantic search with fuzzy matching. Optional - omit to use filters only.",
|
|
151
162
|
sortBy: `Sort order for results. Options: 'relevance' (best semantic match, default), 'created' (newest models), 'price' (cheapest/most expensive, with sortOrder). Defaults to 'relevance'.`,
|
|
152
163
|
cursor: `Opaque pagination cursor from a previous response's \`nextCursor\` field. IMPORTANT: cursors are bound to the exact query text, filters, and sort order that produced them. Reuse the same query+filters+sort when paginating. \`limit\` may change between pages. To start a new search, omit the cursor.`,
|
|
153
|
-
capabilitiesAll: `
|
|
154
|
-
capabilitiesAny: `
|
|
155
|
-
modality: `Required output modality. Filters on the model's output modalities, not input capabilities. For example,
|
|
164
|
+
capabilitiesAll: `Array of capabilities that must ALL be present on the model (AND logic). Valid values: ${CAPABILITIES.join(", ")}. Example: ["function_calling","vision"].`,
|
|
165
|
+
capabilitiesAny: `Array of capabilities where at least ONE must be present (OR logic). Valid values: ${CAPABILITIES.join(", ")}. Example: ["vision","audio_input"].`,
|
|
166
|
+
modality: `Required output modality. Filters on the model's output modalities, not input capabilities. For example, "image" finds image-generation models, while capabilitiesAll=["vision"] finds models that accept image input. Valid values: ${OUTPUT_MODALITIES.join(", ")}.`,
|
|
156
167
|
provider: `Provider prefix filter. Matches model IDs starting with this prefix (e.g., 'openai' matches 'openai/gpt-4o'). Common providers: ${COMMON_PROVIDERS.join(", ")}.`,
|
|
157
168
|
expectedCompletionTokens: `Expected number of completion tokens for cost estimation (default: 256). Typical ranges: 100-500 for quick tests, 1000-2000 for code generation, 4000+ for long-form content. This is a heuristic \u2014 actual billed tokens may differ.`
|
|
158
169
|
};
|
|
159
170
|
var SITE = {
|
|
160
171
|
nav: {
|
|
161
172
|
brand: "index9",
|
|
173
|
+
tools: "Tools",
|
|
174
|
+
howItWorks: "How it works",
|
|
162
175
|
install: "Install",
|
|
163
176
|
faq: "FAQ",
|
|
164
177
|
github: "GitHub",
|
|
@@ -168,19 +181,47 @@ var SITE = {
|
|
|
168
181
|
titleLine1: "Test AI models on your actual prompts, ",
|
|
169
182
|
titleLine2: "not generic benchmarks",
|
|
170
183
|
subtitle: "Compare quality, speed, and cost across 300+ models \u2014 in Cursor, VS Code, or Claude Code.",
|
|
171
|
-
|
|
184
|
+
identity: "index9 is an MCP server that lets your AI coding assistant search, compare, and live-test models from inside your editor.",
|
|
185
|
+
audiencePrefix: "Built for",
|
|
186
|
+
audience: ["AI engineers", "Indie developers", "Teams standardizing on models"],
|
|
187
|
+
pricingNote: "Free. You only pay OpenRouter for live model calls.",
|
|
188
|
+
getStarted: "Add to your editor",
|
|
172
189
|
seeHowItWorks: "See an example",
|
|
173
|
-
updatedBadge: "
|
|
190
|
+
updatedBadge: "Model pricing & specs from OpenRouter \u2014 refreshed "
|
|
191
|
+
},
|
|
192
|
+
howItWorks: {
|
|
193
|
+
label: "How it works",
|
|
194
|
+
heading: "Your assistant picks the right model, automatically",
|
|
195
|
+
subtitle: "index9 runs as an MCP (Model Context Protocol) server. Your editor already speaks MCP \u2014 index9 just plugs in as three extra tools your assistant can call.",
|
|
196
|
+
steps: [
|
|
197
|
+
{
|
|
198
|
+
number: "1",
|
|
199
|
+
title: "You ask your assistant",
|
|
200
|
+
body: '"Pick the cheapest model that can summarize this document well." You chat normally \u2014 no new UI to learn.'
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
number: "2",
|
|
204
|
+
title: "Your assistant calls index9",
|
|
205
|
+
body: "It runs find_models, get_models, and test_model against live OpenRouter data. Results come back with latency, tokens, and cost for your prompt."
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
number: "3",
|
|
209
|
+
title: "You get a measured recommendation",
|
|
210
|
+
body: "The assistant compares real outputs on your real prompt, then recommends the model that fits your constraints. Evidence, not guesswork."
|
|
211
|
+
}
|
|
212
|
+
]
|
|
174
213
|
},
|
|
175
214
|
toolsSection: {
|
|
176
215
|
label: "Tools",
|
|
177
|
-
heading: "Search,
|
|
216
|
+
heading: "Search, inspect, and run live tests",
|
|
217
|
+
subheading: "Three MCP tools your assistant can call. Each maps to one clear job your assistant can do without leaving the chat.",
|
|
178
218
|
openRouterKey: "OpenRouter API key (live tests only)",
|
|
179
219
|
noKeyRequired: "No API key required",
|
|
180
220
|
requiresLabel: "Requires ",
|
|
181
221
|
cards: [
|
|
182
222
|
{
|
|
183
223
|
name: "find_models",
|
|
224
|
+
action: "Search",
|
|
184
225
|
displayName: "find_models",
|
|
185
226
|
fullName: null,
|
|
186
227
|
description: `Search ${MODEL_COUNT} models by what you need \u2014 price, speed, context window, or capabilities like vision and function calling.`,
|
|
@@ -189,14 +230,16 @@ var SITE = {
|
|
|
189
230
|
},
|
|
190
231
|
{
|
|
191
232
|
name: "get_models",
|
|
233
|
+
action: "Inspect",
|
|
192
234
|
displayName: "get_models",
|
|
193
235
|
fullName: null,
|
|
194
|
-
description: "Get current pricing, limits, and capabilities for any model.
|
|
236
|
+
description: "Get current pricing, limits, and capabilities for any model. Synced from OpenRouter every 30 minutes.",
|
|
195
237
|
badge: null,
|
|
196
238
|
requiresKey: false
|
|
197
239
|
},
|
|
198
240
|
{
|
|
199
241
|
name: "test_model",
|
|
242
|
+
action: "Run live tests",
|
|
200
243
|
displayName: "test_model",
|
|
201
244
|
fullName: null,
|
|
202
245
|
description: "Send your prompt to multiple models. Compare outputs, latency, and cost \u2014 measured, not estimated.",
|
|
@@ -234,7 +277,7 @@ var SITE = {
|
|
|
234
277
|
},
|
|
235
278
|
{
|
|
236
279
|
question: "What's the project status?",
|
|
237
|
-
answer: "index9 is
|
|
280
|
+
answer: "index9 is live and used daily. Core tools are stable. Improvements ship through changesets on GitHub; issues and feedback are welcome there.",
|
|
238
281
|
link: null
|
|
239
282
|
},
|
|
240
283
|
{
|
|
@@ -246,7 +289,8 @@ var SITE = {
|
|
|
246
289
|
},
|
|
247
290
|
install: {
|
|
248
291
|
label: "Setup",
|
|
249
|
-
heading: "Add to your
|
|
292
|
+
heading: "Add index9 to your editor",
|
|
293
|
+
subheading: "One line of config for Cursor, VS Code, or Claude Code. Your assistant can start using it immediately.",
|
|
250
294
|
configs: [
|
|
251
295
|
{
|
|
252
296
|
id: "cursor-vscode",
|
|
@@ -288,6 +332,7 @@ var SITE = {
|
|
|
288
332
|
comparison: {
|
|
289
333
|
label: "Comparison",
|
|
290
334
|
heading: "Evidence over intuition",
|
|
335
|
+
subheading: "Benchmark on your real prompts \u2014 not someone else's.",
|
|
291
336
|
withoutLabel: "Without index9",
|
|
292
337
|
withLabel: "With index9",
|
|
293
338
|
withoutItems: [
|
|
@@ -439,16 +484,16 @@ var SearchQuerySchema = z2.object({
|
|
|
439
484
|
minPrice: z2.number().min(0).optional(),
|
|
440
485
|
maxPrice: z2.number().min(0).optional(),
|
|
441
486
|
minContext: z2.number().int().min(1).optional(),
|
|
442
|
-
capabilitiesAll: z2.array(z2.
|
|
443
|
-
capabilitiesAny: z2.array(z2.
|
|
444
|
-
modality: z2.
|
|
487
|
+
capabilitiesAll: z2.array(z2.enum(CAPABILITIES)).optional(),
|
|
488
|
+
capabilitiesAny: z2.array(z2.enum(CAPABILITIES)).optional(),
|
|
489
|
+
modality: z2.enum(OUTPUT_MODALITIES).optional(),
|
|
445
490
|
provider: z2.string().min(1).optional()
|
|
446
491
|
}).strict();
|
|
447
492
|
var SearchResultSchema = z2.object({
|
|
448
|
-
|
|
493
|
+
id: z2.string(),
|
|
449
494
|
name: z2.string(),
|
|
450
495
|
description: z2.string(),
|
|
451
|
-
|
|
496
|
+
created: z2.number().nullable(),
|
|
452
497
|
createdAt: z2.string().nullable(),
|
|
453
498
|
contextLength: z2.number().nullable(),
|
|
454
499
|
maxOutputTokens: z2.number().nullable(),
|
|
@@ -480,7 +525,34 @@ var BatchModelLookupRequestSchema = z3.object({
|
|
|
480
525
|
ids: z3.array(z3.string().min(1)).min(1, "ids are required").max(LIMITS.getModelsMax, `ids must contain between 1 and ${LIMITS.getModelsMax} model IDs`),
|
|
481
526
|
maxDescriptionChars: z3.number().int().min(0).max(2e3).optional()
|
|
482
527
|
}).strict();
|
|
483
|
-
var
|
|
528
|
+
var ModelPricingSchema = z3.object({
|
|
529
|
+
promptPerMillion: z3.number().nullable(),
|
|
530
|
+
completionPerMillion: z3.number().nullable(),
|
|
531
|
+
requestUsd: z3.number().nullable(),
|
|
532
|
+
imageUsd: z3.number().nullable()
|
|
533
|
+
});
|
|
534
|
+
var ModelArchitectureSchema = z3.object({
|
|
535
|
+
inputModalities: z3.array(z3.string()),
|
|
536
|
+
outputModalities: z3.array(z3.string()),
|
|
537
|
+
tokenizer: z3.string().nullable(),
|
|
538
|
+
instructType: z3.string().nullable()
|
|
539
|
+
});
|
|
540
|
+
var ModelResponseSchema = z3.object({
|
|
541
|
+
id: z3.string(),
|
|
542
|
+
canonicalSlug: z3.string().nullable(),
|
|
543
|
+
name: z3.string(),
|
|
544
|
+
description: z3.string(),
|
|
545
|
+
created: z3.number().nullable(),
|
|
546
|
+
createdAt: z3.string().nullable(),
|
|
547
|
+
knowledgeCutoff: z3.string().nullable(),
|
|
548
|
+
contextLength: z3.number().nullable(),
|
|
549
|
+
maxOutputTokens: z3.number().nullable(),
|
|
550
|
+
isModerated: z3.boolean().nullable(),
|
|
551
|
+
pricing: ModelPricingSchema,
|
|
552
|
+
architecture: ModelArchitectureSchema,
|
|
553
|
+
capabilities: z3.array(z3.string()),
|
|
554
|
+
supportedParameters: z3.array(z3.string())
|
|
555
|
+
});
|
|
484
556
|
var BatchModelLookupResponseSchema = z3.object({
|
|
485
557
|
results: z3.array(ModelResponseSchema.nullable()),
|
|
486
558
|
missingIds: z3.array(z3.string()),
|
|
@@ -540,7 +612,7 @@ var TestPricingUsedSchema = z4.object({
|
|
|
540
612
|
var TestModelMetadataSchema = z4.object({
|
|
541
613
|
id: z4.string(),
|
|
542
614
|
name: z4.string(),
|
|
543
|
-
|
|
615
|
+
created: z4.number().nullable().optional(),
|
|
544
616
|
createdAt: z4.string().nullable().optional(),
|
|
545
617
|
pricingUsed: TestPricingUsedSchema.optional()
|
|
546
618
|
});
|
|
@@ -552,7 +624,8 @@ var TestResultSuccessSchema = z4.object({
|
|
|
552
624
|
response: z4.string(),
|
|
553
625
|
latencyMs: z4.number().min(0),
|
|
554
626
|
tokens: UsageTokensSchema,
|
|
555
|
-
cost: z4.number().nullable().optional()
|
|
627
|
+
cost: z4.number().nullable().optional(),
|
|
628
|
+
truncated: z4.boolean().optional()
|
|
556
629
|
});
|
|
557
630
|
var TestResultFailureSchema = z4.object({
|
|
558
631
|
modelId: z4.string(),
|
|
@@ -593,6 +666,13 @@ function loadConfig() {
|
|
|
593
666
|
const env = process.env.INDEX9_API_BASE_URL?.trim();
|
|
594
667
|
const baseUrl = env || DEFAULT_BASE_URL;
|
|
595
668
|
const normalized = baseUrl.replace(/\/$/, "");
|
|
669
|
+
try {
|
|
670
|
+
new URL(normalized);
|
|
671
|
+
} catch {
|
|
672
|
+
throw new Error(
|
|
673
|
+
`Invalid INDEX9_API_BASE_URL: ${normalized}. Expected an absolute URL such as https://index9.dev`
|
|
674
|
+
);
|
|
675
|
+
}
|
|
596
676
|
return {
|
|
597
677
|
baseUrl: normalized,
|
|
598
678
|
apiToken: process.env.INDEX9_API_TOKEN?.trim() || void 0,
|
|
@@ -602,23 +682,62 @@ function loadConfig() {
|
|
|
602
682
|
|
|
603
683
|
// src/client.ts
|
|
604
684
|
var RETRY_DELAYS_MS = [1e3, 2e3, 4e3];
|
|
685
|
+
var ATTEMPT_TIMEOUT_MS = 3e4;
|
|
605
686
|
function isRetryable(status) {
|
|
606
687
|
return status === 429 || status >= 500;
|
|
607
688
|
}
|
|
608
689
|
async function sleep(ms) {
|
|
609
690
|
return new Promise((r) => setTimeout(r, ms));
|
|
610
691
|
}
|
|
692
|
+
function isRetryableError(error) {
|
|
693
|
+
if (!(error instanceof Error)) return false;
|
|
694
|
+
if (error.name === "AbortError" || error.name === "TimeoutError") return true;
|
|
695
|
+
return error instanceof TypeError;
|
|
696
|
+
}
|
|
697
|
+
function toErrorMessage(error) {
|
|
698
|
+
if (error instanceof Error && error.message.trim()) return error.message;
|
|
699
|
+
return "Unknown error";
|
|
700
|
+
}
|
|
611
701
|
async function fetchWithRetry(url, options) {
|
|
612
702
|
let lastResponse = null;
|
|
703
|
+
let lastError;
|
|
613
704
|
for (let i = 0; i <= RETRY_DELAYS_MS.length; i++) {
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
|
|
705
|
+
const timeoutController = new AbortController();
|
|
706
|
+
const timeoutId = setTimeout(() => {
|
|
707
|
+
timeoutController.abort(new DOMException("Request timed out", "AbortError"));
|
|
708
|
+
}, ATTEMPT_TIMEOUT_MS);
|
|
709
|
+
const externalSignal = options.signal;
|
|
710
|
+
const onAbort = () => {
|
|
711
|
+
timeoutController.abort(
|
|
712
|
+
externalSignal?.reason ?? new DOMException("Request aborted", "AbortError")
|
|
713
|
+
);
|
|
714
|
+
};
|
|
715
|
+
if (externalSignal?.aborted) {
|
|
716
|
+
onAbort();
|
|
717
|
+
} else {
|
|
718
|
+
externalSignal?.addEventListener("abort", onAbort, { once: true });
|
|
719
|
+
}
|
|
720
|
+
try {
|
|
721
|
+
const res = await fetch(url, { ...options, signal: timeoutController.signal });
|
|
722
|
+
lastResponse = res;
|
|
723
|
+
if (res.ok || !isRetryable(res.status)) return res;
|
|
724
|
+
} catch (error) {
|
|
725
|
+
lastError = error;
|
|
726
|
+
if (!isRetryableError(error)) {
|
|
727
|
+
throw new Error(`Request failed: ${toErrorMessage(error)}`);
|
|
728
|
+
}
|
|
729
|
+
} finally {
|
|
730
|
+
clearTimeout(timeoutId);
|
|
731
|
+
externalSignal?.removeEventListener("abort", onAbort);
|
|
732
|
+
}
|
|
617
733
|
if (i < RETRY_DELAYS_MS.length) {
|
|
618
734
|
await sleep(RETRY_DELAYS_MS[i]);
|
|
619
735
|
}
|
|
620
736
|
}
|
|
621
|
-
return lastResponse;
|
|
737
|
+
if (lastResponse) return lastResponse;
|
|
738
|
+
throw new Error(
|
|
739
|
+
`Request failed after ${RETRY_DELAYS_MS.length + 1} attempts: ${toErrorMessage(lastError)}`
|
|
740
|
+
);
|
|
622
741
|
}
|
|
623
742
|
function buildUrl(baseUrl, path, params) {
|
|
624
743
|
const url = new URL(path, baseUrl);
|
|
@@ -784,9 +903,9 @@ async function createServer() {
|
|
|
784
903
|
minPrice: z5.number().min(0).optional().describe("Minimum prompt price in USD per million tokens."),
|
|
785
904
|
maxPrice: z5.number().min(0).optional().describe("Maximum prompt price in USD per million tokens."),
|
|
786
905
|
minContext: z5.number().int().min(1).optional().describe("Minimum context window in tokens."),
|
|
787
|
-
capabilitiesAll: z5.array(z5.
|
|
788
|
-
capabilitiesAny: z5.array(z5.
|
|
789
|
-
modality: z5.
|
|
906
|
+
capabilitiesAll: z5.array(z5.enum(CAPABILITIES)).optional().describe(PARAM_DESCRIPTIONS.capabilitiesAll),
|
|
907
|
+
capabilitiesAny: z5.array(z5.enum(CAPABILITIES)).optional().describe(PARAM_DESCRIPTIONS.capabilitiesAny),
|
|
908
|
+
modality: z5.enum(OUTPUT_MODALITIES).optional().describe(PARAM_DESCRIPTIONS.modality),
|
|
790
909
|
provider: z5.string().min(1).optional().describe(PARAM_DESCRIPTIONS.provider)
|
|
791
910
|
},
|
|
792
911
|
annotations: { readOnlyHint: true }
|
package/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@index9/mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,13 +20,14 @@
|
|
|
20
20
|
"access": "public"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
23
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
24
24
|
"zod": "^4.3.6"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@types/node": "^25.
|
|
27
|
+
"@types/node": "^25.6.0",
|
|
28
28
|
"tsup": "^8.5.1",
|
|
29
|
-
"typescript": "
|
|
29
|
+
"typescript": "6.0.3",
|
|
30
|
+
"vitest": "^4.1.5",
|
|
30
31
|
"@index9/core": "2.3.1"
|
|
31
32
|
},
|
|
32
33
|
"engines": {
|
|
@@ -37,6 +38,6 @@
|
|
|
37
38
|
"clean": "rm -rf dist",
|
|
38
39
|
"lint": "tsc --noEmit",
|
|
39
40
|
"start": "node dist/cli.js",
|
|
40
|
-
"test": "
|
|
41
|
+
"test": "vitest run"
|
|
41
42
|
}
|
|
42
43
|
}
|