@preapexis/pi-kit 1.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.
@@ -0,0 +1,49 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import * as path from "path";
3
+ import {
4
+ listFiles,
5
+ readFile,
6
+ checkTypeScriptSyntax,
7
+ hasDefaultExport
8
+ } from "./helpers";
9
+
10
+ describe("extensions", () => {
11
+ const extDir = path.resolve("extensions");
12
+
13
+ it("should have at least one extension file", async () => {
14
+ const files = await listFiles(extDir);
15
+ const tsFiles = files.filter((f) => f.endsWith(".ts"));
16
+ expect(tsFiles.length).toBeGreaterThan(0);
17
+ });
18
+
19
+ it("should export a default function", async () => {
20
+ const files = await listFiles(extDir);
21
+ const tsFiles = files.filter((f) => f.endsWith(".ts"));
22
+
23
+ for (const file of tsFiles) {
24
+ const source = await readFile(file);
25
+ expect(hasDefaultExport(source)).toBe(true);
26
+ }
27
+ });
28
+
29
+ it("should have no unexpected imports", async () => {
30
+ const files = await listFiles(extDir);
31
+ const tsFiles = files.filter((f) => f.endsWith(".ts"));
32
+
33
+ for (const file of tsFiles) {
34
+ const source = await readFile(file);
35
+ const result = checkTypeScriptSyntax(source, path.basename(file));
36
+ expect(result.errors).toEqual([]);
37
+ }
38
+ });
39
+
40
+ it("should not contain duplicate responsibilities (by name)", async () => {
41
+ const files = await listFiles(extDir);
42
+ const tsFiles = files.filter((f) => f.endsWith(".ts"));
43
+
44
+ // Each extension should have a unique, focused name
45
+ const names = tsFiles.map((f) => path.basename(f, ".ts"));
46
+ const uniqueNames = new Set(names);
47
+ expect(uniqueNames.size).toBe(names.length);
48
+ });
49
+ });
@@ -0,0 +1,104 @@
1
+ import * as fs from "fs/promises";
2
+ import * as path from "path";
3
+ import * as ts from "typescript";
4
+
5
+ export async function readFile(filePath: string): Promise<string> {
6
+ return fs.readFile(filePath, "utf-8");
7
+ }
8
+
9
+ export async function listFiles(dir: string): Promise<string[]> {
10
+ const entries = await fs.readdir(dir, { withFileTypes: true });
11
+ const files: string[] = [];
12
+
13
+ for (const entry of entries) {
14
+ if (entry.isFile()) {
15
+ files.push(path.join(dir, entry.name));
16
+ }
17
+ }
18
+
19
+ return files;
20
+ }
21
+
22
+ export async function listDirectories(dir: string): Promise<string[]> {
23
+ const entries = await fs.readdir(dir, { withFileTypes: true });
24
+ return entries
25
+ .filter((e) => e.isDirectory())
26
+ .map((e) => path.join(dir, e.name));
27
+ }
28
+
29
+ export function checkTypeScriptSyntax(source: string, fileName: string): {
30
+ valid: boolean;
31
+ errors: string[];
32
+ } {
33
+ const sourceFile = ts.createSourceFile(
34
+ fileName,
35
+ source,
36
+ ts.ScriptTarget.Latest,
37
+ true
38
+ );
39
+
40
+ const errors: string[] = [];
41
+
42
+ function visit(node: ts.Node) {
43
+ if (ts.isExpressionStatement(node)) {
44
+ const text = node.getText(sourceFile).trim();
45
+ if (text.startsWith("import ") || text.startsWith("export ")) {
46
+ // valid module statement
47
+ } else if (
48
+ text.includes("Unknown") ||
49
+ text.includes("undefined") // crude fallback
50
+ ) {
51
+ // ignore
52
+ }
53
+ }
54
+
55
+ if (ts.isImportDeclaration(node)) {
56
+ const moduleSpecifier = node.moduleSpecifier
57
+ .getText(sourceFile)
58
+ .replace(/['"]/g, "");
59
+
60
+ // Allow @earendil-works/pi-coding-agent and node built-ins
61
+ if (
62
+ moduleSpecifier.startsWith(".") ||
63
+ moduleSpecifier.startsWith("@earendil-works/pi-coding-agent") ||
64
+ moduleSpecifier.startsWith("node:") ||
65
+ ["fs", "fs/promises", "path", "url", "util"].includes(moduleSpecifier)
66
+ ) {
67
+ // ok
68
+ } else {
69
+ errors.push(`Unexpected import: ${moduleSpecifier}`);
70
+ }
71
+ }
72
+
73
+ ts.forEachChild(node, visit);
74
+ }
75
+
76
+ visit(sourceFile);
77
+
78
+ return {
79
+ valid: errors.length === 0,
80
+ errors
81
+ };
82
+ }
83
+
84
+ export function hasDefaultExport(source: string): boolean {
85
+ return /export\s+default\s+function\s*\(/.test(source);
86
+ }
87
+
88
+ export function parseFrontmatter(
89
+ content: string
90
+ ): { name: string; description: string } | null {
91
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
92
+ if (!match) return null;
93
+
94
+ const frontmatter = match[1];
95
+ const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
96
+ const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
97
+
98
+ if (!nameMatch || !descMatch) return null;
99
+
100
+ return {
101
+ name: nameMatch[1].trim(),
102
+ description: descMatch[1].trim()
103
+ };
104
+ }
@@ -0,0 +1,38 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import * as fs from "fs/promises";
3
+
4
+ describe("package.json", () => {
5
+ it("should exist and be valid JSON", async () => {
6
+ const raw = await fs.readFile("package.json", "utf-8");
7
+ const pkg = JSON.parse(raw);
8
+ expect(pkg).toBeDefined();
9
+ });
10
+
11
+ it("should have required pi package fields", async () => {
12
+ const raw = await fs.readFile("package.json", "utf-8");
13
+ const pkg = JSON.parse(raw);
14
+
15
+ expect(pkg.name).toBe("@preapexis/pi-kit");
16
+ expect(pkg.pi).toBeDefined();
17
+ expect(Array.isArray(pkg.pi.extensions)).toBe(true);
18
+ expect(Array.isArray(pkg.pi.prompts)).toBe(true);
19
+ expect(Array.isArray(pkg.pi.skills)).toBe(true);
20
+ expect(Array.isArray(pkg.pi.themes)).toBe(true);
21
+ });
22
+
23
+ it("should declare peer dependency on pi-coding-agent", async () => {
24
+ const raw = await fs.readFile("package.json", "utf-8");
25
+ const pkg = JSON.parse(raw);
26
+
27
+ expect(pkg.peerDependencies).toBeDefined();
28
+ expect(pkg.peerDependencies["@earendil-works/pi-coding-agent"]).toBe("*");
29
+ });
30
+
31
+ it("should have test script configured", async () => {
32
+ const raw = await fs.readFile("package.json", "utf-8");
33
+ const pkg = JSON.parse(raw);
34
+
35
+ expect(pkg.scripts?.test).toBeDefined();
36
+ expect(pkg.scripts.test).toContain("vitest");
37
+ });
38
+ });
@@ -0,0 +1,40 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import * as path from "path";
3
+ import { listFiles, readFile } from "./helpers";
4
+
5
+ describe("prompts", () => {
6
+ const promptsDir = path.resolve("prompts");
7
+
8
+ it("should have at least one prompt file", async () => {
9
+ const files = await listFiles(promptsDir);
10
+ const mdFiles = files.filter((f) => f.endsWith(".md"));
11
+ expect(mdFiles.length).toBeGreaterThan(0);
12
+ });
13
+
14
+ it("should not include prompts.md (reserved for /prompts command)", async () => {
15
+ const files = await listFiles(promptsDir);
16
+ const names = files.map((f) => path.basename(f));
17
+ expect(names).not.toContain("prompts.md");
18
+ });
19
+
20
+ it("should be non-empty markdown files", async () => {
21
+ const files = await listFiles(promptsDir);
22
+ const mdFiles = files.filter((f) => f.endsWith(".md"));
23
+
24
+ for (const file of mdFiles) {
25
+ const content = await readFile(file);
26
+ expect(content.trim().length).toBeGreaterThan(0);
27
+ }
28
+ });
29
+
30
+ it("should have filenames that become valid slash commands", async () => {
31
+ const files = await listFiles(promptsDir);
32
+ const mdFiles = files.filter((f) => f.endsWith(".md"));
33
+
34
+ for (const file of mdFiles) {
35
+ const name = path.basename(file, ".md");
36
+ // Slash commands should be lowercase with hyphens
37
+ expect(name).toMatch(/^[a-z0-9-]+$/);
38
+ }
39
+ });
40
+ });
@@ -0,0 +1,42 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import * as path from "path";
3
+ import { listDirectories, readFile, parseFrontmatter } from "./helpers";
4
+
5
+ describe("skills", () => {
6
+ const skillsDir = path.resolve("skills");
7
+
8
+ it("should have at least one skill folder", async () => {
9
+ const dirs = await listDirectories(skillsDir);
10
+ expect(dirs.length).toBeGreaterThan(0);
11
+ });
12
+
13
+ it("each skill folder should contain a SKILL.md", async () => {
14
+ const dirs = await listDirectories(skillsDir);
15
+
16
+ for (const dir of dirs) {
17
+ const skillFile = path.join(dir, "SKILL.md");
18
+ const content = await readFile(skillFile).catch(() => null);
19
+ expect(content).not.toBeNull();
20
+ }
21
+ });
22
+
23
+ it("each SKILL.md should have valid frontmatter with name and description", async () => {
24
+ const dirs = await listDirectories(skillsDir);
25
+
26
+ for (const dir of dirs) {
27
+ const skillFile = path.join(dir, "SKILL.md");
28
+ const content = await readFile(skillFile);
29
+ const frontmatter = parseFrontmatter(content);
30
+
31
+ expect(frontmatter).not.toBeNull();
32
+ expect(frontmatter!.name).toBeTruthy();
33
+ expect(frontmatter!.description).toBeTruthy();
34
+ }
35
+ });
36
+
37
+ it("should not have skill files directly inside skills/ (must be in subfolders)", async () => {
38
+ const entries = await listDirectories(skillsDir);
39
+ // listDirectories only returns directories, which is what we want
40
+ expect(entries.length).toBeGreaterThan(0);
41
+ });
42
+ });
@@ -0,0 +1,49 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import * as path from "path";
3
+ import { listFiles, readFile } from "./helpers";
4
+
5
+ describe("themes", () => {
6
+ const themesDir = path.resolve("themes");
7
+
8
+ it("should have at least one theme file", async () => {
9
+ const files = await listFiles(themesDir);
10
+ const jsonFiles = files.filter((f) => f.endsWith(".json"));
11
+ expect(jsonFiles.length).toBeGreaterThan(0);
12
+ });
13
+
14
+ it("should be valid JSON", async () => {
15
+ const files = await listFiles(themesDir);
16
+ const jsonFiles = files.filter((f) => f.endsWith(".json"));
17
+
18
+ for (const file of jsonFiles) {
19
+ const content = await readFile(file);
20
+ expect(() => JSON.parse(content)).not.toThrow();
21
+ }
22
+ });
23
+
24
+ it("should have required theme fields", async () => {
25
+ const files = await listFiles(themesDir);
26
+ const jsonFiles = files.filter((f) => f.endsWith(".json"));
27
+
28
+ for (const file of jsonFiles) {
29
+ const content = await readFile(file);
30
+ const theme = JSON.parse(content);
31
+
32
+ expect(theme.name).toBeTruthy();
33
+ expect(theme.vars).toBeDefined();
34
+ expect(theme.colors).toBeDefined();
35
+ }
36
+ });
37
+
38
+ it("should reference the Pi theme schema", async () => {
39
+ const files = await listFiles(themesDir);
40
+ const jsonFiles = files.filter((f) => f.endsWith(".json"));
41
+
42
+ for (const file of jsonFiles) {
43
+ const content = await readFile(file);
44
+ const theme = JSON.parse(content);
45
+
46
+ expect(theme.$schema).toMatch(/theme-schema\.json$/);
47
+ }
48
+ });
49
+ });
@@ -0,0 +1,77 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
3
+ "name": "latte-review",
4
+ "vars": {
5
+ "bg": "#fafaf9",
6
+ "panel": "#f5f5f4",
7
+ "panel2": "#e7e5e4",
8
+ "muted": "#78716c",
9
+ "dim": "#a8a29e",
10
+ "blue": "#2563eb",
11
+ "cyan": "#0891b2",
12
+ "green": "#16a34a",
13
+ "red": "#dc2626",
14
+ "yellow": "#ca8a04",
15
+ "purple": "#7c3aed",
16
+ "orange": "#ea580c"
17
+ },
18
+ "colors": {
19
+ "accent": "blue",
20
+ "border": "panel2",
21
+ "borderAccent": "blue",
22
+ "borderMuted": "dim",
23
+ "success": "green",
24
+ "error": "red",
25
+ "warning": "yellow",
26
+ "muted": "muted",
27
+ "dim": "dim",
28
+ "text": "",
29
+ "thinkingText": "muted",
30
+
31
+ "selectedBg": "panel2",
32
+ "userMessageBg": "panel",
33
+ "userMessageText": "",
34
+ "customMessageBg": "#e0f2fe",
35
+ "customMessageText": "",
36
+ "customMessageLabel": "blue",
37
+ "toolPendingBg": "#eff6ff",
38
+ "toolSuccessBg": "#dcfce7",
39
+ "toolErrorBg": "#fee2e2",
40
+ "toolTitle": "blue",
41
+ "toolOutput": "",
42
+
43
+ "mdHeading": "purple",
44
+ "mdLink": "blue",
45
+ "mdLinkUrl": "muted",
46
+ "mdCode": "cyan",
47
+ "mdCodeBlock": "",
48
+ "mdCodeBlockBorder": "panel2",
49
+ "mdQuote": "muted",
50
+ "mdQuoteBorder": "dim",
51
+ "mdHr": "panel2",
52
+ "mdListBullet": "orange",
53
+
54
+ "toolDiffAdded": "green",
55
+ "toolDiffRemoved": "red",
56
+ "toolDiffContext": "muted",
57
+
58
+ "syntaxComment": "muted",
59
+ "syntaxKeyword": "purple",
60
+ "syntaxFunction": "blue",
61
+ "syntaxVariable": "orange",
62
+ "syntaxString": "green",
63
+ "syntaxNumber": "cyan",
64
+ "syntaxType": "blue",
65
+ "syntaxOperator": "purple",
66
+ "syntaxPunctuation": "muted",
67
+
68
+ "thinkingOff": "dim",
69
+ "thinkingMinimal": "blue",
70
+ "thinkingLow": "cyan",
71
+ "thinkingMedium": "yellow",
72
+ "thinkingHigh": "purple",
73
+ "thinkingXhigh": "red",
74
+
75
+ "bashMode": "orange"
76
+ }
77
+ }
@@ -0,0 +1,77 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
3
+ "name": "neon-guardian",
4
+ "vars": {
5
+ "bg": "#0b1020",
6
+ "panel": "#111827",
7
+ "panel2": "#172033",
8
+ "muted": 245,
9
+ "dim": 240,
10
+ "cyan": "#22d3ee",
11
+ "blue": "#38bdf8",
12
+ "green": "#22c55e",
13
+ "red": "#fb7185",
14
+ "yellow": "#facc15",
15
+ "purple": "#c084fc",
16
+ "pink": "#f472b6"
17
+ },
18
+ "colors": {
19
+ "accent": "cyan",
20
+ "border": "panel2",
21
+ "borderAccent": "cyan",
22
+ "borderMuted": "dim",
23
+ "success": "green",
24
+ "error": "red",
25
+ "warning": "yellow",
26
+ "muted": "muted",
27
+ "dim": "dim",
28
+ "text": "",
29
+ "thinkingText": "muted",
30
+
31
+ "selectedBg": "panel2",
32
+ "userMessageBg": "panel",
33
+ "userMessageText": "",
34
+ "customMessageBg": "#0e7490",
35
+ "customMessageText": "",
36
+ "customMessageLabel": "yellow",
37
+ "toolPendingBg": "#0f172a",
38
+ "toolSuccessBg": "#052e16",
39
+ "toolErrorBg": "#450a0a",
40
+ "toolTitle": "cyan",
41
+ "toolOutput": "",
42
+
43
+ "mdHeading": "purple",
44
+ "mdLink": "cyan",
45
+ "mdLinkUrl": "muted",
46
+ "mdCode": "pink",
47
+ "mdCodeBlock": "",
48
+ "mdCodeBlockBorder": "panel2",
49
+ "mdQuote": "muted",
50
+ "mdQuoteBorder": "cyan",
51
+ "mdHr": "panel2",
52
+ "mdListBullet": "cyan",
53
+
54
+ "toolDiffAdded": "green",
55
+ "toolDiffRemoved": "red",
56
+ "toolDiffContext": "muted",
57
+
58
+ "syntaxComment": "muted",
59
+ "syntaxKeyword": "purple",
60
+ "syntaxFunction": "blue",
61
+ "syntaxVariable": "yellow",
62
+ "syntaxString": "green",
63
+ "syntaxNumber": "pink",
64
+ "syntaxType": "cyan",
65
+ "syntaxOperator": "purple",
66
+ "syntaxPunctuation": "muted",
67
+
68
+ "thinkingOff": "dim",
69
+ "thinkingMinimal": "blue",
70
+ "thinkingLow": "cyan",
71
+ "thinkingMedium": "yellow",
72
+ "thinkingHigh": "purple",
73
+ "thinkingXhigh": "red",
74
+
75
+ "bashMode": "yellow"
76
+ }
77
+ }
@@ -0,0 +1,75 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
3
+ "name": "safe-dark",
4
+ "vars": {
5
+ "bg": "#18181b",
6
+ "panel": "#27272a",
7
+ "soft": "#3f3f46",
8
+ "muted": 245,
9
+ "blue": "#38bdf8",
10
+ "green": "#22c55e",
11
+ "red": "#ef4444",
12
+ "yellow": "#facc15",
13
+ "purple": "#a78bfa",
14
+ "cyan": "#22d3ee"
15
+ },
16
+ "colors": {
17
+ "accent": "blue",
18
+ "border": "soft",
19
+ "borderAccent": "blue",
20
+ "borderMuted": "muted",
21
+ "success": "green",
22
+ "error": "red",
23
+ "warning": "yellow",
24
+ "muted": "muted",
25
+ "dim": 240,
26
+ "text": "",
27
+ "thinkingText": "muted",
28
+
29
+ "selectedBg": "panel",
30
+ "userMessageBg": "panel",
31
+ "userMessageText": "",
32
+ "customMessageBg": "panel",
33
+ "customMessageText": "",
34
+ "customMessageLabel": "blue",
35
+ "toolPendingBg": "#1e293b",
36
+ "toolSuccessBg": "#052e16",
37
+ "toolErrorBg": "#450a0a",
38
+ "toolTitle": "blue",
39
+ "toolOutput": "",
40
+
41
+ "mdHeading": "purple",
42
+ "mdLink": "blue",
43
+ "mdLinkUrl": "muted",
44
+ "mdCode": "cyan",
45
+ "mdCodeBlock": "",
46
+ "mdCodeBlockBorder": "soft",
47
+ "mdQuote": "muted",
48
+ "mdQuoteBorder": "soft",
49
+ "mdHr": "soft",
50
+ "mdListBullet": "cyan",
51
+
52
+ "toolDiffAdded": "green",
53
+ "toolDiffRemoved": "red",
54
+ "toolDiffContext": "muted",
55
+
56
+ "syntaxComment": "muted",
57
+ "syntaxKeyword": "purple",
58
+ "syntaxFunction": "blue",
59
+ "syntaxVariable": "yellow",
60
+ "syntaxString": "green",
61
+ "syntaxNumber": "cyan",
62
+ "syntaxType": "blue",
63
+ "syntaxOperator": "purple",
64
+ "syntaxPunctuation": "muted",
65
+
66
+ "thinkingOff": "muted",
67
+ "thinkingMinimal": "blue",
68
+ "thinkingLow": "cyan",
69
+ "thinkingMedium": "yellow",
70
+ "thinkingHigh": "purple",
71
+ "thinkingXhigh": "red",
72
+
73
+ "bashMode": "yellow"
74
+ }
75
+ }
@@ -0,0 +1,77 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
3
+ "name": "tokyo-midnight",
4
+ "vars": {
5
+ "bg": "#1a1b26",
6
+ "panel": "#24283b",
7
+ "panel2": "#292e42",
8
+ "muted": "#565f89",
9
+ "dim": 240,
10
+ "blue": "#7aa2f7",
11
+ "cyan": "#7dcfff",
12
+ "green": "#9ece6a",
13
+ "red": "#f7768e",
14
+ "yellow": "#e0af68",
15
+ "purple": "#bb9af7",
16
+ "orange": "#ff9e64"
17
+ },
18
+ "colors": {
19
+ "accent": "blue",
20
+ "border": "panel2",
21
+ "borderAccent": "blue",
22
+ "borderMuted": "muted",
23
+ "success": "green",
24
+ "error": "red",
25
+ "warning": "yellow",
26
+ "muted": "muted",
27
+ "dim": "dim",
28
+ "text": "",
29
+ "thinkingText": "muted",
30
+
31
+ "selectedBg": "panel2",
32
+ "userMessageBg": "panel",
33
+ "userMessageText": "",
34
+ "customMessageBg": "panel",
35
+ "customMessageText": "",
36
+ "customMessageLabel": "blue",
37
+ "toolPendingBg": "#202436",
38
+ "toolSuccessBg": "#1f3324",
39
+ "toolErrorBg": "#3b2028",
40
+ "toolTitle": "blue",
41
+ "toolOutput": "",
42
+
43
+ "mdHeading": "purple",
44
+ "mdLink": "blue",
45
+ "mdLinkUrl": "muted",
46
+ "mdCode": "cyan",
47
+ "mdCodeBlock": "",
48
+ "mdCodeBlockBorder": "muted",
49
+ "mdQuote": "muted",
50
+ "mdQuoteBorder": "panel2",
51
+ "mdHr": "panel2",
52
+ "mdListBullet": "orange",
53
+
54
+ "toolDiffAdded": "green",
55
+ "toolDiffRemoved": "red",
56
+ "toolDiffContext": "muted",
57
+
58
+ "syntaxComment": "muted",
59
+ "syntaxKeyword": "purple",
60
+ "syntaxFunction": "blue",
61
+ "syntaxVariable": "orange",
62
+ "syntaxString": "green",
63
+ "syntaxNumber": "yellow",
64
+ "syntaxType": "cyan",
65
+ "syntaxOperator": "purple",
66
+ "syntaxPunctuation": "muted",
67
+
68
+ "thinkingOff": "muted",
69
+ "thinkingMinimal": "blue",
70
+ "thinkingLow": "cyan",
71
+ "thinkingMedium": "yellow",
72
+ "thinkingHigh": "purple",
73
+ "thinkingXhigh": "red",
74
+
75
+ "bashMode": "orange"
76
+ }
77
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "resolveJsonModule": true,
11
+ "noEmit": true
12
+ },
13
+ "include": ["extensions/**/*.ts", "tests/**/*.ts", "vitest.config.ts"]
14
+ }
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: "node"
7
+ }
8
+ });