@hasna/skills 0.1.19 → 0.1.20

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 (51) hide show
  1. package/README.md +136 -18
  2. package/bin/index.js +33877 -33924
  3. package/bin/mcp.js +196 -95
  4. package/dist/cli/commands/completion.d.ts +5 -0
  5. package/dist/cli/commands/create-sync-config.d.ts +5 -0
  6. package/dist/cli/commands/diagnostic.d.ts +5 -0
  7. package/dist/cli/commands/init.d.ts +5 -0
  8. package/dist/cli/commands/install.d.ts +5 -0
  9. package/dist/cli/commands/introspect.d.ts +5 -0
  10. package/dist/cli/commands/list.d.ts +5 -0
  11. package/dist/cli/commands/runtime.d.ts +5 -0
  12. package/dist/cli/commands/schedule.d.ts +5 -0
  13. package/dist/index.js +197 -97
  14. package/dist/lib/config.d.ts +1 -1
  15. package/dist/lib/registry.d.ts +1 -11
  16. package/dist/lib/scheduler.d.ts +1 -1
  17. package/dist/lib/scheduler.test.d.ts +4 -0
  18. package/dist/lib/search.d.ts +17 -0
  19. package/package.json +1 -1
  20. package/skills/skill-commitpush/SKILL.md +57 -0
  21. package/skills/skill-commitpush/package.json +34 -0
  22. package/skills/skill-commitpush/src/index.ts +34 -0
  23. package/skills/skill-commitpush/tsconfig.json +17 -0
  24. package/skills/skill-commitpushpr/SKILL.md +55 -0
  25. package/skills/skill-commitpushpr/package.json +34 -0
  26. package/skills/skill-commitpushpr/src/index.ts +34 -0
  27. package/skills/skill-commitpushpr/tsconfig.json +17 -0
  28. package/skills/skill-monitor/SKILL.md +69 -0
  29. package/skills/skill-monitor/package.json +34 -0
  30. package/skills/skill-monitor/src/index.ts +34 -0
  31. package/skills/skill-monitor/tsconfig.json +17 -0
  32. package/skills/skill-read-csv/SKILL.md +62 -0
  33. package/skills/skill-read-csv/package.json +38 -0
  34. package/skills/skill-read-csv/src/index.ts +331 -0
  35. package/skills/skill-read-csv/tsconfig.json +17 -0
  36. package/skills/skill-read-excel/SKILL.md +64 -0
  37. package/skills/skill-read-excel/package.json +37 -0
  38. package/skills/skill-read-excel/src/index.ts +253 -0
  39. package/skills/skill-read-excel/tsconfig.json +17 -0
  40. package/skills/skill-read-image/SKILL.md +47 -0
  41. package/skills/skill-read-image/package.json +34 -0
  42. package/skills/skill-read-image/src/index.ts +264 -0
  43. package/skills/skill-read-image/tsconfig.json +17 -0
  44. package/skills/skill-read-pdf/SKILL.md +52 -0
  45. package/skills/skill-read-pdf/package.json +37 -0
  46. package/skills/skill-read-pdf/src/index.ts +376 -0
  47. package/skills/skill-read-pdf/tsconfig.json +17 -0
  48. package/skills/skill-tmux-session/SKILL.md +109 -0
  49. package/skills/skill-tmux-session/package.json +34 -0
  50. package/skills/skill-tmux-session/src/index.ts +34 -0
  51. package/skills/skill-tmux-session/tsconfig.json +17 -0
@@ -0,0 +1,376 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { mkdir, readFile, writeFile } from "fs/promises";
4
+ import { dirname, resolve } from "path";
5
+ import { PDFDocument } from "pdf-lib";
6
+
7
+ const VERSION = "0.1.0";
8
+ const DEFAULT_MODEL = process.env.ANTHROPIC_MODEL || "claude-sonnet-4-20250514";
9
+ const DEFAULT_PROMPT =
10
+ "Extract the text, tables, and structured content from this PDF. Preserve headings, bullets, and table structure.";
11
+ const API_URL = process.env.ANTHROPIC_API_URL || "https://api.anthropic.com/v1/messages";
12
+ const MAX_CHUNK_SIZE = 20;
13
+ const MAX_REQUEST_BYTES = 32 * 1024 * 1024;
14
+
15
+ type OutputFormat = "json" | "markdown" | "text";
16
+
17
+ interface CliOptions {
18
+ input?: string;
19
+ pages?: string;
20
+ prompt: string;
21
+ format: OutputFormat;
22
+ model: string;
23
+ chunkSize: number;
24
+ maxTokens: number;
25
+ output?: string;
26
+ text: boolean;
27
+ }
28
+
29
+ interface PdfChunkResult {
30
+ chunk: number;
31
+ pages: number[];
32
+ response: unknown;
33
+ }
34
+
35
+ interface PdfResult {
36
+ input: string;
37
+ model: string;
38
+ prompt: string;
39
+ format: OutputFormat;
40
+ totalPages: number;
41
+ requestedPages: number[];
42
+ chunkSize: number;
43
+ chunks: PdfChunkResult[];
44
+ mergedText: string;
45
+ }
46
+
47
+ function printHelp(): void {
48
+ console.log(`skill-read-pdf v${VERSION}
49
+
50
+ USAGE:
51
+ skill-read-pdf --input <path-or-url> [options]
52
+
53
+ OPTIONS:
54
+ -i, --input <path-or-url> PDF file path or remote URL
55
+ --pages <ranges> Page ranges like 1-3,5,8-10
56
+ -p, --prompt <text> Extraction prompt
57
+ -f, --format <value> json | markdown | text
58
+ -m, --model <name> Anthropic model to call
59
+ --chunk-size <n> Pages per request (max 20)
60
+ --max-tokens <n> Maximum response tokens per chunk
61
+ -o, --output <path> Save result to a file
62
+ --text Print only merged chunk text
63
+ --help Show this help message
64
+ --version Show the current version
65
+ `);
66
+ }
67
+
68
+ function parseArgs(argv: string[]): CliOptions {
69
+ const options: CliOptions = {
70
+ prompt: DEFAULT_PROMPT,
71
+ format: "markdown",
72
+ model: DEFAULT_MODEL,
73
+ chunkSize: MAX_CHUNK_SIZE,
74
+ maxTokens: 1600,
75
+ text: false,
76
+ };
77
+
78
+ for (let i = 0; i < argv.length; i += 1) {
79
+ const arg = argv[i];
80
+ switch (arg) {
81
+ case "--help":
82
+ case "-h":
83
+ printHelp();
84
+ process.exit(0);
85
+ case "--version":
86
+ case "-v":
87
+ console.log(VERSION);
88
+ process.exit(0);
89
+ case "--input":
90
+ case "-i":
91
+ options.input = argv[++i];
92
+ break;
93
+ case "--pages":
94
+ options.pages = argv[++i];
95
+ break;
96
+ case "--prompt":
97
+ case "-p":
98
+ options.prompt = argv[++i] ?? DEFAULT_PROMPT;
99
+ break;
100
+ case "--format":
101
+ case "-f": {
102
+ const value = (argv[++i] ?? "markdown").toLowerCase();
103
+ if (value !== "json" && value !== "markdown" && value !== "text") {
104
+ throw new Error(`Invalid --format value: ${value}`);
105
+ }
106
+ options.format = value;
107
+ break;
108
+ }
109
+ case "--model":
110
+ case "-m":
111
+ options.model = argv[++i] ?? DEFAULT_MODEL;
112
+ break;
113
+ case "--chunk-size": {
114
+ const value = Number.parseInt(argv[++i] ?? "", 10);
115
+ if (!Number.isFinite(value) || value <= 0 || value > MAX_CHUNK_SIZE) {
116
+ throw new Error(`--chunk-size must be between 1 and ${MAX_CHUNK_SIZE}`);
117
+ }
118
+ options.chunkSize = value;
119
+ break;
120
+ }
121
+ case "--max-tokens": {
122
+ const value = Number.parseInt(argv[++i] ?? "", 10);
123
+ if (!Number.isFinite(value) || value <= 0) {
124
+ throw new Error(`Invalid --max-tokens value: ${argv[i]}`);
125
+ }
126
+ options.maxTokens = value;
127
+ break;
128
+ }
129
+ case "--output":
130
+ case "-o":
131
+ options.output = argv[++i];
132
+ break;
133
+ case "--text":
134
+ options.text = true;
135
+ break;
136
+ default:
137
+ if (arg.startsWith("-")) {
138
+ throw new Error(`Unknown option: ${arg}`);
139
+ }
140
+ if (!options.input) {
141
+ options.input = arg;
142
+ break;
143
+ }
144
+ throw new Error(`Unexpected argument: ${arg}`);
145
+ }
146
+ }
147
+
148
+ if (!options.input) {
149
+ throw new Error("Missing required --input <path-or-url> argument");
150
+ }
151
+
152
+ return options;
153
+ }
154
+
155
+ function isUrl(value: string): boolean {
156
+ try {
157
+ const url = new URL(value);
158
+ return url.protocol === "http:" || url.protocol === "https:";
159
+ } catch {
160
+ return false;
161
+ }
162
+ }
163
+
164
+ async function loadPdfBytes(input: string): Promise<{ input: string; bytes: Uint8Array }> {
165
+ if (isUrl(input)) {
166
+ const response = await fetch(input);
167
+ if (!response.ok) {
168
+ throw new Error(`Failed to download PDF: ${response.status} ${response.statusText}`);
169
+ }
170
+ return {
171
+ input,
172
+ bytes: new Uint8Array(await response.arrayBuffer()),
173
+ };
174
+ }
175
+
176
+ const resolvedInput = resolve(input);
177
+ return {
178
+ input: resolvedInput,
179
+ bytes: await readFile(resolvedInput),
180
+ };
181
+ }
182
+
183
+ function parsePageRanges(spec: string | undefined, totalPages: number): number[] {
184
+ if (!spec) {
185
+ return Array.from({ length: totalPages }, (_, index) => index + 1);
186
+ }
187
+
188
+ const pages = new Set<number>();
189
+ for (const part of spec.split(",")) {
190
+ const trimmed = part.trim();
191
+ if (!trimmed) continue;
192
+ if (trimmed.includes("-")) {
193
+ const [startText, endText] = trimmed.split("-");
194
+ const start = Number.parseInt(startText ?? "", 10);
195
+ const end = Number.parseInt(endText ?? "", 10);
196
+ if (!Number.isFinite(start) || !Number.isFinite(end) || start <= 0 || end <= 0 || end < start) {
197
+ throw new Error(`Invalid page range: ${trimmed}`);
198
+ }
199
+ for (let page = start; page <= end; page += 1) {
200
+ if (page > totalPages) {
201
+ throw new Error(`Requested page ${page} exceeds total pages (${totalPages})`);
202
+ }
203
+ pages.add(page);
204
+ }
205
+ continue;
206
+ }
207
+
208
+ const page = Number.parseInt(trimmed, 10);
209
+ if (!Number.isFinite(page) || page <= 0 || page > totalPages) {
210
+ throw new Error(`Invalid page number: ${trimmed}`);
211
+ }
212
+ pages.add(page);
213
+ }
214
+
215
+ return Array.from(pages).sort((a, b) => a - b);
216
+ }
217
+
218
+ function chunkPages(pages: number[], size: number): number[][] {
219
+ const chunks: number[][] = [];
220
+ for (let index = 0; index < pages.length; index += size) {
221
+ chunks.push(pages.slice(index, index + size));
222
+ }
223
+ return chunks;
224
+ }
225
+
226
+ async function buildChunkPdf(sourcePdf: PDFDocument, pages: number[]): Promise<Uint8Array> {
227
+ const output = await PDFDocument.create();
228
+ const copiedPages = await output.copyPages(sourcePdf, pages.map((page) => page - 1));
229
+ for (const page of copiedPages) {
230
+ output.addPage(page);
231
+ }
232
+ return output.save();
233
+ }
234
+
235
+ function formatInstruction(format: OutputFormat): string {
236
+ switch (format) {
237
+ case "json":
238
+ return "Return valid JSON with keys summary, text, tables, and structure.";
239
+ case "text":
240
+ return "Return plain text only. Keep the important structure readable.";
241
+ case "markdown":
242
+ default:
243
+ return "Return markdown. Preserve headings, lists, and tables when possible.";
244
+ }
245
+ }
246
+
247
+ async function callAnthropicPdf(
248
+ pdfBytes: Uint8Array,
249
+ model: string,
250
+ maxTokens: number,
251
+ prompt: string,
252
+ ): Promise<{ raw: unknown; text: string }> {
253
+ if (pdfBytes.byteLength > MAX_REQUEST_BYTES) {
254
+ throw new Error(`Chunk exceeds 32MB request limit (${pdfBytes.byteLength} bytes)`);
255
+ }
256
+
257
+ const apiKey = process.env.ANTHROPIC_API_KEY;
258
+ if (!apiKey) {
259
+ throw new Error("Missing ANTHROPIC_API_KEY");
260
+ }
261
+
262
+ const response = await fetch(API_URL, {
263
+ method: "POST",
264
+ headers: {
265
+ "content-type": "application/json",
266
+ "x-api-key": apiKey,
267
+ "anthropic-version": "2023-06-01",
268
+ },
269
+ body: JSON.stringify({
270
+ model,
271
+ max_tokens: maxTokens,
272
+ messages: [
273
+ {
274
+ role: "user",
275
+ content: [
276
+ {
277
+ type: "document",
278
+ source: {
279
+ type: "base64",
280
+ media_type: "application/pdf",
281
+ data: Buffer.from(pdfBytes).toString("base64"),
282
+ },
283
+ },
284
+ {
285
+ type: "text",
286
+ text: prompt,
287
+ },
288
+ ],
289
+ },
290
+ ],
291
+ }),
292
+ });
293
+
294
+ const payload = await response.json() as {
295
+ content?: Array<{ type: string; text?: string }>;
296
+ error?: { message?: string };
297
+ };
298
+
299
+ if (!response.ok) {
300
+ throw new Error(payload.error?.message || `Anthropic request failed with status ${response.status}`);
301
+ }
302
+
303
+ const text = (payload.content ?? [])
304
+ .filter((entry) => entry.type === "text" && typeof entry.text === "string")
305
+ .map((entry) => entry.text)
306
+ .join("\n\n");
307
+
308
+ return { raw: payload, text };
309
+ }
310
+
311
+ async function writeOutput(path: string, value: string): Promise<void> {
312
+ await mkdir(dirname(path), { recursive: true });
313
+ await writeFile(path, value, "utf8");
314
+ }
315
+
316
+ async function main(): Promise<void> {
317
+ const options = parseArgs(process.argv.slice(2));
318
+ const { input, bytes } = await loadPdfBytes(options.input!);
319
+ const sourcePdf = await PDFDocument.load(bytes);
320
+ const totalPages = sourcePdf.getPageCount();
321
+ const requestedPages = parsePageRanges(options.pages, totalPages);
322
+ const pageChunks = chunkPages(requestedPages, options.chunkSize);
323
+ const chunkResults: PdfChunkResult[] = [];
324
+ const mergedParts: string[] = [];
325
+
326
+ for (let index = 0; index < pageChunks.length; index += 1) {
327
+ const pages = pageChunks[index];
328
+ const chunkPdf = await buildChunkPdf(sourcePdf, pages);
329
+ const prompt = [
330
+ options.prompt,
331
+ formatInstruction(options.format),
332
+ `This chunk covers pages ${pages.join(", ")}.`,
333
+ `Chunk ${index + 1} of ${pageChunks.length}.`,
334
+ ].join("\n\n");
335
+ const response = await callAnthropicPdf(chunkPdf, options.model, options.maxTokens, prompt);
336
+ chunkResults.push({
337
+ chunk: index + 1,
338
+ pages,
339
+ response: options.format === "json" ? tryParseJson(response.text) : response.text,
340
+ });
341
+ mergedParts.push(response.text);
342
+ }
343
+
344
+ const result: PdfResult = {
345
+ input,
346
+ model: options.model,
347
+ prompt: options.prompt,
348
+ format: options.format,
349
+ totalPages,
350
+ requestedPages,
351
+ chunkSize: options.chunkSize,
352
+ chunks: chunkResults,
353
+ mergedText: mergedParts.join("\n\n"),
354
+ };
355
+
356
+ const output = options.text ? `${result.mergedText}\n` : `${JSON.stringify(result, null, 2)}\n`;
357
+ if (options.output) {
358
+ await writeOutput(resolve(options.output), output);
359
+ } else {
360
+ process.stdout.write(output);
361
+ }
362
+ }
363
+
364
+ function tryParseJson(value: string): unknown {
365
+ try {
366
+ return JSON.parse(value);
367
+ } catch {
368
+ return value;
369
+ }
370
+ }
371
+
372
+ main().catch((error) => {
373
+ const message = error instanceof Error ? error.message : String(error);
374
+ process.stderr.write(`skill-read-pdf: ${message}\n`);
375
+ process.exit(1);
376
+ });
@@ -0,0 +1,17 @@
1
+ {
2
+ "extends": "../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "module": "ESNext",
5
+ "outDir": "./dist",
6
+ "rootDir": "./src",
7
+ "resolveJsonModule": true,
8
+ "noEmit": true
9
+ },
10
+ "include": [
11
+ "src/**/*"
12
+ ],
13
+ "exclude": [
14
+ "node_modules",
15
+ "dist"
16
+ ]
17
+ }
@@ -0,0 +1,109 @@
1
+ ---
2
+ name: skill-tmux-session
3
+ description: Create and manage grouped tmux sessions where each session focuses on its own window and its own folder. Knows workspace layout, naming conventions, and both multi-project and multi-agent patterns.
4
+ user_invocable: true
5
+ ---
6
+
7
+ # skill-tmux-session
8
+
9
+ Use this skill when you need to add a window and session to an existing group, or create a new group from scratch. These are grouped tmux sessions where every named session locks onto its own window so `tmux attach -t <name>` lands directly in the intended project or tool.
10
+
11
+ ## Workspace Layout
12
+
13
+ ```text
14
+ ~/workspace/ Linux (spark01, spark02) — always lowercase
15
+ ~/Workspace/ macOS (apple01) — always capital W
16
+
17
+ [workspace]/
18
+ └── [division]/
19
+ └── [area]/
20
+ └── [area][status]/
21
+ └── [prefix]-[name]/
22
+ ```
23
+
24
+ Examples:
25
+
26
+ ```text
27
+ workspace/hasna/opensource/opensourcedev/open-todos
28
+ workspace/hasna/opensource/opensourcedev/open-conversations
29
+ workspace/hasnaxyz/agent/agentdev/agent-claude
30
+ workspace/hasnaxyz/service/servicedev/service-chat
31
+ workspace/hasnastudio/hasnastudio-alumia/platform/platform-alumia
32
+ workspace/hasnaxyz/internalapp/iapp-takumi
33
+ ```
34
+
35
+ Local tools live under `~/local/local-[name]/`.
36
+
37
+ ## How grouped sessions work
38
+
39
+ - A source session owns the windows.
40
+ - Named sessions join that source session and lock onto one window index.
41
+ - `tmux attach -t open-economy` should land in the `open-economy` window without manual switching.
42
+
43
+ ## When to open a session
44
+
45
+ | Work type | Source session | Session/window naming |
46
+ |-----------|----------------|-----------------------|
47
+ | Multiple OSS repos | `opensourcemaintain` | `open-[name]` |
48
+ | Multiple agent repos | `agentdev` | `agent-[name]` |
49
+ | Multiple local tools | `local` | `local-[name]` |
50
+ | Multiple workers on one project | project name | `[project]-01`, `[project]-02`, ... |
51
+
52
+ Rule: tmux groups live at the area/team level, not the individual repo level.
53
+
54
+ ## Add a window to an existing group
55
+
56
+ ```bash
57
+ SOURCE="opensourcemaintain"
58
+ NAME="open-economy"
59
+ DIR="$HOME/workspace/hasna/opensource/opensourcedev/open-economy"
60
+
61
+ tmux new-window -t "$SOURCE" -n "$NAME" -c "$DIR"
62
+ IDX=$(tmux list-windows -t "$SOURCE" -F "#{window_index}:#{window_name}" | grep ":${NAME}$" | cut -d: -f1)
63
+ tmux new-session -d -s "$NAME" -t "$SOURCE"
64
+ tmux select-window -t "${NAME}:${IDX}"
65
+ ```
66
+
67
+ ## Create a new group
68
+
69
+ ```bash
70
+ SOURCE="opensourcemaintain"
71
+ BASE="$HOME/workspace/hasna/opensource/opensourcedev"
72
+
73
+ WINDOWS=(
74
+ "open-todos:$BASE/open-todos"
75
+ "open-conversations:$BASE/open-conversations"
76
+ "open-mementos:$BASE/open-mementos"
77
+ )
78
+
79
+ tmux new-session -d -s "$SOURCE" -n "open-todos" -c "$BASE/open-todos"
80
+
81
+ for entry in "${WINDOWS[@]:1}"; do
82
+ tmux new-window -t "$SOURCE" -n "$(echo "$entry" | cut -d: -f1)" -c "$(echo "$entry" | cut -d: -f2)"
83
+ done
84
+
85
+ for entry in "${WINDOWS[@]}"; do
86
+ name=$(echo "$entry" | cut -d: -f1)
87
+ idx=$(tmux list-windows -t "$SOURCE" -F "#{window_index}:#{window_name}" | grep ":${name}$" | cut -d: -f1)
88
+ tmux new-session -d -s "$name" -t "$SOURCE"
89
+ tmux select-window -t "${name}:${idx}"
90
+ done
91
+ ```
92
+
93
+ ## Naming rules
94
+
95
+ - OSS project: `open-[name]`
96
+ - Agent repo: `agent-[name]`
97
+ - Service repo: `service-[name]`
98
+ - Local tool: `local-[name]`
99
+ - Multi-agent on one project: `[project]-01..N`
100
+
101
+ Always use the full prefixed name for both the window name and the session name.
102
+
103
+ ## Verification
104
+
105
+ ```bash
106
+ tmux display-message -t open-economy -p "#{window_index}:#{window_name} @ #{pane_current_path}"
107
+ tmux list-sessions | grep "(group opensourcemaintain)"
108
+ tmux list-panes -a -F "#{session_name}:#{window_name} dead=#{pane_dead}" | grep "dead=1"
109
+ ```
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@hasnaxyz/skill-tmux-session",
3
+ "version": "0.1.0",
4
+ "description": "Instruction-set skill for creating grouped tmux sessions with repo-aware naming",
5
+ "type": "module",
6
+ "main": "src/index.ts",
7
+ "bin": {
8
+ "skill-tmux-session": "src/index.ts"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "SKILL.md",
13
+ "tsconfig.json"
14
+ ],
15
+ "publishConfig": {
16
+ "access": "restricted"
17
+ },
18
+ "keywords": [
19
+ "tmux",
20
+ "sessions",
21
+ "workspace",
22
+ "automation",
23
+ "skill"
24
+ ],
25
+ "license": "Apache-2.0",
26
+ "scripts": {
27
+ "start": "bun run src/index.ts",
28
+ "typecheck": "tsc --noEmit"
29
+ },
30
+ "devDependencies": {
31
+ "@types/bun": "latest",
32
+ "typescript": "^5.7.0"
33
+ }
34
+ }
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env bun
2
+
3
+ const VERSION = "0.1.0";
4
+
5
+ function printHelp(): void {
6
+ console.log(`skill-tmux-session - Instruction-set skill
7
+
8
+ DESCRIPTION:
9
+ Guides an agent through grouped tmux session setup with workspace-aware naming:
10
+ - create source sessions
11
+ - add windows for repos or local tools
12
+ - attach named sessions to the correct window index
13
+ - verify pane focus and dead sessions
14
+
15
+ USAGE:
16
+ skills docs tmux-session
17
+ skills install tmux-session --for claude
18
+ `);
19
+ }
20
+
21
+ const args = process.argv.slice(2);
22
+
23
+ if (args.includes("--version") || args.includes("-v")) {
24
+ console.log(VERSION);
25
+ process.exit(0);
26
+ }
27
+
28
+ if (args.includes("--help") || args.includes("-h") || args.length === 0) {
29
+ printHelp();
30
+ process.exit(0);
31
+ }
32
+
33
+ console.log("This is an instruction-set skill. Install it for an agent with:");
34
+ console.log(" skills install tmux-session --for claude");
@@ -0,0 +1,17 @@
1
+ {
2
+ "extends": "../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "module": "ESNext",
5
+ "outDir": "./dist",
6
+ "rootDir": "./src",
7
+ "resolveJsonModule": true,
8
+ "noEmit": true
9
+ },
10
+ "include": [
11
+ "src/**/*"
12
+ ],
13
+ "exclude": [
14
+ "node_modules",
15
+ "dist"
16
+ ]
17
+ }