@nightowlsdev/mcp-server 0.1.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/LICENSE +21 -0
- package/dist/index.cjs +102 -0
- package/dist/index.d.cts +46 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +74 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Night Owls contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
createSwarmMcpServer: () => createSwarmMcpServer,
|
|
24
|
+
runSwarmMcpStdio: () => runSwarmMcpStdio
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/server.ts
|
|
29
|
+
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
30
|
+
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
31
|
+
var import_zod = require("zod");
|
|
32
|
+
var ok = (payload) => ({ content: [{ type: "text", text: JSON.stringify(payload) }] });
|
|
33
|
+
var err = (message) => ({ content: [{ type: "text", text: JSON.stringify({ status: "error", message }) }], isError: true });
|
|
34
|
+
async function drain(iter, runId) {
|
|
35
|
+
let answer = "";
|
|
36
|
+
for await (const e of iter) {
|
|
37
|
+
if (e.type === "swarm.message" && e.data.role === "assistant") {
|
|
38
|
+
answer += e.data.text ?? e.data.delta ?? "";
|
|
39
|
+
} else if (e.type === "swarm.question") {
|
|
40
|
+
return ok({ status: "needs_input", question: e.data.prompt, followupId: e.data.followupId, partial: answer || void 0, runId });
|
|
41
|
+
} else if (e.type === "swarm.run_failed") {
|
|
42
|
+
return ok({ status: "failed", message: e.data.message, stage: e.data.stage, runId });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return ok({ status: "done", answer, runId });
|
|
46
|
+
}
|
|
47
|
+
async function handleAsk(opts, slug, args, extra) {
|
|
48
|
+
try {
|
|
49
|
+
const { engine, storage } = opts;
|
|
50
|
+
const auth = await opts.resolveContext(extra);
|
|
51
|
+
if (!auth) return err("unauthorized: resolveContext returned null");
|
|
52
|
+
const newId = opts.newId ?? (() => crypto.randomUUID());
|
|
53
|
+
if (args.followupId && args.answer != null) {
|
|
54
|
+
const found = await storage.runs.findSuspended(auth.tenantId, args.followupId);
|
|
55
|
+
if (!found) return err(`no suspended run for followup ${args.followupId}`);
|
|
56
|
+
const row = await storage.runs.get(found.runId);
|
|
57
|
+
if (!row || row.tenantId !== auth.tenantId) return err("forbidden");
|
|
58
|
+
const ctx2 = { tenantId: auth.tenantId, userId: auth.userId, agentSlug: row.agentSlug, runId: found.runId, threadId: row.threadId };
|
|
59
|
+
return await drain(engine.resume({ runId: found.runId, toolCallId: found.toolCallId, followupId: args.followupId, answer: args.answer, context: args.context }, ctx2), found.runId);
|
|
60
|
+
}
|
|
61
|
+
if (!args.message) return err("message is required for a new ask (or pass followupId + answer to resume)");
|
|
62
|
+
const runId = newId();
|
|
63
|
+
const threadId = args.threadId ?? newId();
|
|
64
|
+
const ctx = { tenantId: auth.tenantId, userId: auth.userId, agentSlug: slug, runId, threadId };
|
|
65
|
+
return await drain(engine.run({ message: args.message, context: args.context }, ctx), runId);
|
|
66
|
+
} catch (e) {
|
|
67
|
+
return err(e instanceof Error ? e.message : String(e));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function createSwarmMcpServer(opts) {
|
|
71
|
+
const server = new import_mcp.McpServer({ name: opts.name ?? "nightowls-swarm", version: opts.version ?? "0.1.0" });
|
|
72
|
+
for (const agent of opts.agents) {
|
|
73
|
+
const label = agent.name ?? agent.slug;
|
|
74
|
+
server.registerTool(
|
|
75
|
+
`ask_${agent.slug}`,
|
|
76
|
+
{
|
|
77
|
+
title: `Ask ${label}`,
|
|
78
|
+
description: agent.description ?? `Ask the ${label} agent${agent.role ? ` (${agent.role})` : ""}. Returns its answer, or { status: "needs_input", question, followupId } when it needs more from you \u2014 call this tool again with { followupId, answer } to continue the same conversation.`,
|
|
79
|
+
inputSchema: {
|
|
80
|
+
message: import_zod.z.string().optional().describe("what to ask the agent (required for a new ask)"),
|
|
81
|
+
threadId: import_zod.z.string().optional().describe("continue an existing conversation; omit to start a new one"),
|
|
82
|
+
context: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional().describe("untrusted/advisory page context"),
|
|
83
|
+
followupId: import_zod.z.string().optional().describe("from a prior needs_input result \u2014 to answer the agent"),
|
|
84
|
+
answer: import_zod.z.string().optional().describe("your answer to the agent's question (with followupId)")
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
(args, extra) => handleAsk(opts, agent.slug, args, extra)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return server;
|
|
91
|
+
}
|
|
92
|
+
async function runSwarmMcpStdio(opts) {
|
|
93
|
+
const server = createSwarmMcpServer(opts);
|
|
94
|
+
await server.connect(new import_stdio.StdioServerTransport());
|
|
95
|
+
console.error(`[nightowls] swarm MCP server ready on stdio (${opts.agents.length} ask_<agent> tools)`);
|
|
96
|
+
return server;
|
|
97
|
+
}
|
|
98
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
99
|
+
0 && (module.exports = {
|
|
100
|
+
createSwarmMcpServer,
|
|
101
|
+
runSwarmMcpStdio
|
|
102
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { SwarmEngine, StorageAdapter } from '@nightowlsdev/core';
|
|
3
|
+
|
|
4
|
+
/** The caller identity resolved from an MCP request — the ONLY source of tenancy (never tool args). */
|
|
5
|
+
interface SwarmMcpContext {
|
|
6
|
+
tenantId: string;
|
|
7
|
+
userId: string;
|
|
8
|
+
}
|
|
9
|
+
/** One agent to expose as an `ask_<slug>` tool. Pass `await engine.listAgents(ctx)` (AgentSummary[]) directly,
|
|
10
|
+
* or a hand-built list. `name`/`role`/`description` only shape the tool's model-facing description. */
|
|
11
|
+
interface SwarmMcpAgent {
|
|
12
|
+
slug: string;
|
|
13
|
+
name?: string;
|
|
14
|
+
role?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
}
|
|
17
|
+
interface SwarmMcpServerOpts {
|
|
18
|
+
engine: SwarmEngine;
|
|
19
|
+
storage: StorageAdapter;
|
|
20
|
+
/** The agents to expose (their slugs become `ask_<slug>` tools). Tool names are tenant-independent; the
|
|
21
|
+
* per-request tenant (from `resolveContext`) selects each tenant's actual agent version at run time. */
|
|
22
|
+
agents: SwarmMcpAgent[];
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the caller's identity from the MCP request's per-handler `extra` (transport/auth info). Return
|
|
25
|
+
* `null` to reject the call as unauthorized. Identity is taken ONLY from here — never from tool arguments,
|
|
26
|
+
* which are attacker-controllable. stdio/local: a fixed context; HTTP: read `extra.authInfo`/headers.
|
|
27
|
+
*/
|
|
28
|
+
resolveContext: (extra: unknown) => Promise<SwarmMcpContext | null> | SwarmMcpContext | null;
|
|
29
|
+
/** Id generator for runId/threadId — injectable for deterministic tests. Default `crypto.randomUUID`. */
|
|
30
|
+
newId?: () => string;
|
|
31
|
+
name?: string;
|
|
32
|
+
version?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Build an MCP server that exposes a Night Owls swarm as `ask_<agent>` tools (R15). Multi-turn HITL: an agent that
|
|
36
|
+
* asks the human mid-run returns `{ status: "needs_input", question, followupId }`; the caller answers by
|
|
37
|
+
* calling the same tool again with `{ followupId, answer }`. Serves machine callers, human MCP-client UIs, and
|
|
38
|
+
* services uniformly. Engine-wall-safe: raw MCP SDK + core types only, no engine-vendor imports; exposes ONLY
|
|
39
|
+
* `ask_<agent>` (never `run_<workflow>`).
|
|
40
|
+
*/
|
|
41
|
+
declare function createSwarmMcpServer(opts: SwarmMcpServerOpts): McpServer;
|
|
42
|
+
/** Convenience: build the server and serve it over stdio. All logs go to stderr (stdout is the JSON-RPC
|
|
43
|
+
* channel). Resolves with the connected server (await its transport close to keep the process alive). */
|
|
44
|
+
declare function runSwarmMcpStdio(opts: SwarmMcpServerOpts): Promise<McpServer>;
|
|
45
|
+
|
|
46
|
+
export { type SwarmMcpAgent, type SwarmMcpContext, type SwarmMcpServerOpts, createSwarmMcpServer, runSwarmMcpStdio };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { SwarmEngine, StorageAdapter } from '@nightowlsdev/core';
|
|
3
|
+
|
|
4
|
+
/** The caller identity resolved from an MCP request — the ONLY source of tenancy (never tool args). */
|
|
5
|
+
interface SwarmMcpContext {
|
|
6
|
+
tenantId: string;
|
|
7
|
+
userId: string;
|
|
8
|
+
}
|
|
9
|
+
/** One agent to expose as an `ask_<slug>` tool. Pass `await engine.listAgents(ctx)` (AgentSummary[]) directly,
|
|
10
|
+
* or a hand-built list. `name`/`role`/`description` only shape the tool's model-facing description. */
|
|
11
|
+
interface SwarmMcpAgent {
|
|
12
|
+
slug: string;
|
|
13
|
+
name?: string;
|
|
14
|
+
role?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
}
|
|
17
|
+
interface SwarmMcpServerOpts {
|
|
18
|
+
engine: SwarmEngine;
|
|
19
|
+
storage: StorageAdapter;
|
|
20
|
+
/** The agents to expose (their slugs become `ask_<slug>` tools). Tool names are tenant-independent; the
|
|
21
|
+
* per-request tenant (from `resolveContext`) selects each tenant's actual agent version at run time. */
|
|
22
|
+
agents: SwarmMcpAgent[];
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the caller's identity from the MCP request's per-handler `extra` (transport/auth info). Return
|
|
25
|
+
* `null` to reject the call as unauthorized. Identity is taken ONLY from here — never from tool arguments,
|
|
26
|
+
* which are attacker-controllable. stdio/local: a fixed context; HTTP: read `extra.authInfo`/headers.
|
|
27
|
+
*/
|
|
28
|
+
resolveContext: (extra: unknown) => Promise<SwarmMcpContext | null> | SwarmMcpContext | null;
|
|
29
|
+
/** Id generator for runId/threadId — injectable for deterministic tests. Default `crypto.randomUUID`. */
|
|
30
|
+
newId?: () => string;
|
|
31
|
+
name?: string;
|
|
32
|
+
version?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Build an MCP server that exposes a Night Owls swarm as `ask_<agent>` tools (R15). Multi-turn HITL: an agent that
|
|
36
|
+
* asks the human mid-run returns `{ status: "needs_input", question, followupId }`; the caller answers by
|
|
37
|
+
* calling the same tool again with `{ followupId, answer }`. Serves machine callers, human MCP-client UIs, and
|
|
38
|
+
* services uniformly. Engine-wall-safe: raw MCP SDK + core types only, no engine-vendor imports; exposes ONLY
|
|
39
|
+
* `ask_<agent>` (never `run_<workflow>`).
|
|
40
|
+
*/
|
|
41
|
+
declare function createSwarmMcpServer(opts: SwarmMcpServerOpts): McpServer;
|
|
42
|
+
/** Convenience: build the server and serve it over stdio. All logs go to stderr (stdout is the JSON-RPC
|
|
43
|
+
* channel). Resolves with the connected server (await its transport close to keep the process alive). */
|
|
44
|
+
declare function runSwarmMcpStdio(opts: SwarmMcpServerOpts): Promise<McpServer>;
|
|
45
|
+
|
|
46
|
+
export { type SwarmMcpAgent, type SwarmMcpContext, type SwarmMcpServerOpts, createSwarmMcpServer, runSwarmMcpStdio };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// src/server.ts
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
var ok = (payload) => ({ content: [{ type: "text", text: JSON.stringify(payload) }] });
|
|
6
|
+
var err = (message) => ({ content: [{ type: "text", text: JSON.stringify({ status: "error", message }) }], isError: true });
|
|
7
|
+
async function drain(iter, runId) {
|
|
8
|
+
let answer = "";
|
|
9
|
+
for await (const e of iter) {
|
|
10
|
+
if (e.type === "swarm.message" && e.data.role === "assistant") {
|
|
11
|
+
answer += e.data.text ?? e.data.delta ?? "";
|
|
12
|
+
} else if (e.type === "swarm.question") {
|
|
13
|
+
return ok({ status: "needs_input", question: e.data.prompt, followupId: e.data.followupId, partial: answer || void 0, runId });
|
|
14
|
+
} else if (e.type === "swarm.run_failed") {
|
|
15
|
+
return ok({ status: "failed", message: e.data.message, stage: e.data.stage, runId });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return ok({ status: "done", answer, runId });
|
|
19
|
+
}
|
|
20
|
+
async function handleAsk(opts, slug, args, extra) {
|
|
21
|
+
try {
|
|
22
|
+
const { engine, storage } = opts;
|
|
23
|
+
const auth = await opts.resolveContext(extra);
|
|
24
|
+
if (!auth) return err("unauthorized: resolveContext returned null");
|
|
25
|
+
const newId = opts.newId ?? (() => crypto.randomUUID());
|
|
26
|
+
if (args.followupId && args.answer != null) {
|
|
27
|
+
const found = await storage.runs.findSuspended(auth.tenantId, args.followupId);
|
|
28
|
+
if (!found) return err(`no suspended run for followup ${args.followupId}`);
|
|
29
|
+
const row = await storage.runs.get(found.runId);
|
|
30
|
+
if (!row || row.tenantId !== auth.tenantId) return err("forbidden");
|
|
31
|
+
const ctx2 = { tenantId: auth.tenantId, userId: auth.userId, agentSlug: row.agentSlug, runId: found.runId, threadId: row.threadId };
|
|
32
|
+
return await drain(engine.resume({ runId: found.runId, toolCallId: found.toolCallId, followupId: args.followupId, answer: args.answer, context: args.context }, ctx2), found.runId);
|
|
33
|
+
}
|
|
34
|
+
if (!args.message) return err("message is required for a new ask (or pass followupId + answer to resume)");
|
|
35
|
+
const runId = newId();
|
|
36
|
+
const threadId = args.threadId ?? newId();
|
|
37
|
+
const ctx = { tenantId: auth.tenantId, userId: auth.userId, agentSlug: slug, runId, threadId };
|
|
38
|
+
return await drain(engine.run({ message: args.message, context: args.context }, ctx), runId);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
return err(e instanceof Error ? e.message : String(e));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function createSwarmMcpServer(opts) {
|
|
44
|
+
const server = new McpServer({ name: opts.name ?? "nightowls-swarm", version: opts.version ?? "0.1.0" });
|
|
45
|
+
for (const agent of opts.agents) {
|
|
46
|
+
const label = agent.name ?? agent.slug;
|
|
47
|
+
server.registerTool(
|
|
48
|
+
`ask_${agent.slug}`,
|
|
49
|
+
{
|
|
50
|
+
title: `Ask ${label}`,
|
|
51
|
+
description: agent.description ?? `Ask the ${label} agent${agent.role ? ` (${agent.role})` : ""}. Returns its answer, or { status: "needs_input", question, followupId } when it needs more from you \u2014 call this tool again with { followupId, answer } to continue the same conversation.`,
|
|
52
|
+
inputSchema: {
|
|
53
|
+
message: z.string().optional().describe("what to ask the agent (required for a new ask)"),
|
|
54
|
+
threadId: z.string().optional().describe("continue an existing conversation; omit to start a new one"),
|
|
55
|
+
context: z.record(z.string(), z.unknown()).optional().describe("untrusted/advisory page context"),
|
|
56
|
+
followupId: z.string().optional().describe("from a prior needs_input result \u2014 to answer the agent"),
|
|
57
|
+
answer: z.string().optional().describe("your answer to the agent's question (with followupId)")
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
(args, extra) => handleAsk(opts, agent.slug, args, extra)
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
return server;
|
|
64
|
+
}
|
|
65
|
+
async function runSwarmMcpStdio(opts) {
|
|
66
|
+
const server = createSwarmMcpServer(opts);
|
|
67
|
+
await server.connect(new StdioServerTransport());
|
|
68
|
+
console.error(`[nightowls] swarm MCP server ready on stdio (${opts.agents.length} ask_<agent> tools)`);
|
|
69
|
+
return server;
|
|
70
|
+
}
|
|
71
|
+
export {
|
|
72
|
+
createSwarmMcpServer,
|
|
73
|
+
runSwarmMcpStdio
|
|
74
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nightowlsdev/mcp-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/cueplusplus/corale.git",
|
|
12
|
+
"directory": "packages/mcp-server"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/cueplusplus/corale#readme",
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js",
|
|
20
|
+
"require": "./dist/index.cjs"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"main": "./dist/index.cjs",
|
|
24
|
+
"module": "./dist/index.js",
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
31
|
+
"zod": "^4.0.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@nightowlsdev/core": "0.3.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^24.12.4",
|
|
38
|
+
"ai": "^6.0.0",
|
|
39
|
+
"tsup": "8.5.1",
|
|
40
|
+
"typescript": "6.0.3",
|
|
41
|
+
"vitest": "^3.2.0",
|
|
42
|
+
"@nightowlsdev/core": "0.3.0",
|
|
43
|
+
"@nightowlsdev/eslint-config": "0.0.0",
|
|
44
|
+
"@nightowlsdev/tsconfig": "0.0.0"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsup",
|
|
48
|
+
"typecheck": "tsc --noEmit",
|
|
49
|
+
"test": "vitest run",
|
|
50
|
+
"lint": "eslint src"
|
|
51
|
+
}
|
|
52
|
+
}
|