@lite-agent/sdk 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/dist/index.d.ts +72 -4
- package/dist/index.js +161 -23
- package/package.json +10 -9
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ModelProvider, Tool, Middleware, Sandbox, PermissionPolicy, ApprovalHandler, InputHandler, Agent, Message, AgentEvent, RunResult, ToolContext } from '@lite-agent/core';
|
|
1
|
+
import { ModelProvider, Tool, Middleware, Sandbox, Store, Compactor, PermissionPolicy, ApprovalHandler, InputHandler, Agent, Message, AgentEvent, RunResult, ToolContext, SpillStore } from '@lite-agent/core';
|
|
2
2
|
export * from '@lite-agent/core';
|
|
3
3
|
import { ZodType } from 'zod';
|
|
4
4
|
|
|
@@ -15,6 +15,21 @@ interface CreateLiteAgentConfig {
|
|
|
15
15
|
maxTokens?: number;
|
|
16
16
|
use?: Middleware[];
|
|
17
17
|
sandbox?: Sandbox;
|
|
18
|
+
store?: Store;
|
|
19
|
+
/** Override the global home (default `$LITE_AGENT_HOME` || `~/.lite-agent`). */
|
|
20
|
+
home?: string;
|
|
21
|
+
/** Persist transcripts under the project's sessions dir. Default true. Ignored when `store` is set. */
|
|
22
|
+
sessions?: boolean;
|
|
23
|
+
/** Spill oversized tool_results to disk + register `read_spilled`. Default true. */
|
|
24
|
+
spill?: boolean | {
|
|
25
|
+
budgetBytes?: number;
|
|
26
|
+
};
|
|
27
|
+
/** Proactive compactor. Default deterministic `defaultCompactor`; `false` disables compaction. */
|
|
28
|
+
compactor?: Compactor | false;
|
|
29
|
+
/** Sweep stale spill/session files once at startup. Default true (30 days). */
|
|
30
|
+
cleanup?: boolean | {
|
|
31
|
+
maxAgeDays?: number;
|
|
32
|
+
};
|
|
18
33
|
permission?: PermissionPolicy;
|
|
19
34
|
onApproval?: ApprovalHandler;
|
|
20
35
|
onAskUser?: InputHandler;
|
|
@@ -37,6 +52,16 @@ interface QueryOptions {
|
|
|
37
52
|
signal?: AbortSignal;
|
|
38
53
|
sessionId?: string;
|
|
39
54
|
sandbox?: Sandbox;
|
|
55
|
+
store?: Store;
|
|
56
|
+
compactor?: Compactor | false;
|
|
57
|
+
home?: string;
|
|
58
|
+
sessions?: boolean;
|
|
59
|
+
spill?: boolean | {
|
|
60
|
+
budgetBytes?: number;
|
|
61
|
+
};
|
|
62
|
+
cleanup?: boolean | {
|
|
63
|
+
maxAgeDays?: number;
|
|
64
|
+
};
|
|
40
65
|
permission?: PermissionPolicy;
|
|
41
66
|
onApproval?: ApprovalHandler;
|
|
42
67
|
onAskUser?: InputHandler;
|
|
@@ -64,15 +89,58 @@ declare function askUserTool(): Tool;
|
|
|
64
89
|
declare function defaultTools(workdir: string): Tool[];
|
|
65
90
|
|
|
66
91
|
declare class SkillLoader {
|
|
67
|
-
readonly
|
|
92
|
+
readonly dirs: string[];
|
|
68
93
|
private skills;
|
|
69
|
-
constructor(
|
|
94
|
+
constructor(dirs: string | string[]);
|
|
70
95
|
private loadAll;
|
|
71
96
|
private parse;
|
|
72
97
|
getDescriptions(): string;
|
|
98
|
+
names(): string[];
|
|
73
99
|
getContent(name: string): string;
|
|
74
100
|
}
|
|
75
101
|
|
|
76
102
|
declare function loadSkillTool(loader: SkillLoader): Tool;
|
|
77
103
|
|
|
78
|
-
|
|
104
|
+
interface JsonlStoreOptions {
|
|
105
|
+
/** Directory holding one `<sessionId>.jsonl` transcript per session. */
|
|
106
|
+
dir: string;
|
|
107
|
+
}
|
|
108
|
+
declare function jsonlStore(opts: JsonlStoreOptions): Store;
|
|
109
|
+
|
|
110
|
+
/** Global home: `$LITE_AGENT_HOME` if set, else `~/.lite-agent`. */
|
|
111
|
+
declare function liteAgentHome(): string;
|
|
112
|
+
/** Stable per absolute project path: first 16 hex of sha1(resolve(workdir)). */
|
|
113
|
+
declare function projectHash(workdir: string): string;
|
|
114
|
+
interface ProjectPaths {
|
|
115
|
+
home: string;
|
|
116
|
+
hash: string;
|
|
117
|
+
spillDir: string;
|
|
118
|
+
sessionsDir: string;
|
|
119
|
+
globalSkillsDir: string;
|
|
120
|
+
projectSkillsDir: string;
|
|
121
|
+
}
|
|
122
|
+
/** Pure: derive every path from `workdir` (+ optional home). No fs side effects. */
|
|
123
|
+
declare function resolveProjectPaths(opts: {
|
|
124
|
+
workdir: string;
|
|
125
|
+
home?: string;
|
|
126
|
+
}): ProjectPaths;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Delete stale runtime files under <home>/projects/HASH/spill and
|
|
130
|
+
* <home>/projects/HASH/sessions whose mtime is older than maxAgeDays
|
|
131
|
+
* (default 30). Global sweep, synchronous, fully guarded -- a failure
|
|
132
|
+
* here must never block agent startup.
|
|
133
|
+
*/
|
|
134
|
+
declare function sweepStale(opts?: {
|
|
135
|
+
home?: string;
|
|
136
|
+
maxAgeDays?: number;
|
|
137
|
+
}): void;
|
|
138
|
+
|
|
139
|
+
interface FileSpillStoreOptions {
|
|
140
|
+
/** Directory holding one `<ref>.txt` blob per spilled tool result. */
|
|
141
|
+
dir: string;
|
|
142
|
+
}
|
|
143
|
+
declare function fileSpillStore(opts: FileSpillStoreOptions): SpillStore;
|
|
144
|
+
declare function readSpilledTool(store: SpillStore): Tool;
|
|
145
|
+
|
|
146
|
+
export { type CreateLiteAgentConfig, type FileSpillStoreOptions, type JsonlStoreOptions, type ProjectPaths, type QueryOptions, SkillLoader, type SystemPromptOptions, askUserTool, bashTool, buildSystemPrompt, createLiteAgent, defaultTools, fileSpillStore, fileTools, jsonlStore, liteAgentHome, loadSkillTool, makeSafePath, projectHash, query, readSpilledTool, resolveProjectPaths, sweepStale, todoTool, tool };
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,14 @@
|
|
|
2
2
|
export * from "@lite-agent/core";
|
|
3
3
|
|
|
4
4
|
// src/createLiteAgent.ts
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
createAgent,
|
|
7
|
+
nativeCodec,
|
|
8
|
+
permission,
|
|
9
|
+
compaction,
|
|
10
|
+
reactiveCompaction,
|
|
11
|
+
defaultCompactor
|
|
12
|
+
} from "@lite-agent/core";
|
|
6
13
|
|
|
7
14
|
// src/tools/bash.ts
|
|
8
15
|
import { execSync } from "child_process";
|
|
@@ -188,15 +195,15 @@ function defaultTools(workdir) {
|
|
|
188
195
|
// src/skills/loader.ts
|
|
189
196
|
import { existsSync, readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
190
197
|
import { dirname as dirname2, join } from "path";
|
|
198
|
+
import matter from "gray-matter";
|
|
191
199
|
var SkillLoader = class {
|
|
192
|
-
|
|
200
|
+
dirs;
|
|
193
201
|
skills = {};
|
|
194
|
-
constructor(
|
|
195
|
-
this.
|
|
202
|
+
constructor(dirs) {
|
|
203
|
+
this.dirs = Array.isArray(dirs) ? dirs : [dirs];
|
|
196
204
|
this.loadAll();
|
|
197
205
|
}
|
|
198
206
|
loadAll() {
|
|
199
|
-
if (!existsSync(this.skillsDir)) return;
|
|
200
207
|
const walk = (dir) => {
|
|
201
208
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
202
209
|
const p = join(dir, entry.name);
|
|
@@ -208,27 +215,27 @@ var SkillLoader = class {
|
|
|
208
215
|
}
|
|
209
216
|
}
|
|
210
217
|
};
|
|
211
|
-
|
|
218
|
+
for (const dir of this.dirs) {
|
|
219
|
+
if (existsSync(dir)) walk(dir);
|
|
220
|
+
}
|
|
212
221
|
}
|
|
213
222
|
parse(text) {
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
const meta = {};
|
|
217
|
-
for (const line of m[1].trim().split("\n")) {
|
|
218
|
-
const i = line.indexOf(":");
|
|
219
|
-
if (i > 0) meta[line.slice(0, i).trim()] = line.slice(i + 1).trim();
|
|
220
|
-
}
|
|
221
|
-
return { meta, body: m[2].trim() };
|
|
223
|
+
const { data, content } = matter(text);
|
|
224
|
+
return { meta: data, body: content.trim() };
|
|
222
225
|
}
|
|
223
226
|
getDescriptions() {
|
|
224
227
|
const names = Object.keys(this.skills);
|
|
225
228
|
if (!names.length) return "(no skills available)";
|
|
226
229
|
return names.map((n) => {
|
|
227
230
|
const s = this.skills[n];
|
|
228
|
-
const
|
|
231
|
+
const tagList = Array.isArray(s.meta.tags) ? s.meta.tags.join(", ") : s.meta.tags;
|
|
232
|
+
const tags = tagList ? ` [${tagList}]` : "";
|
|
229
233
|
return ` - ${n}: ${s.meta.description ?? "No description"}${tags}`;
|
|
230
234
|
}).join("\n");
|
|
231
235
|
}
|
|
236
|
+
names() {
|
|
237
|
+
return Object.keys(this.skills);
|
|
238
|
+
}
|
|
232
239
|
getContent(name) {
|
|
233
240
|
const s = this.skills[name];
|
|
234
241
|
if (!s) return `Error: Unknown skill '${name}'. Available: ${Object.keys(this.skills).join(", ")}`;
|
|
@@ -270,27 +277,144 @@ Available skills:
|
|
|
270
277
|
${opts.skills}`;
|
|
271
278
|
}
|
|
272
279
|
|
|
280
|
+
// src/paths.ts
|
|
281
|
+
import { homedir } from "os";
|
|
282
|
+
import { resolve as resolve2, join as join2 } from "path";
|
|
283
|
+
import { createHash } from "crypto";
|
|
284
|
+
function liteAgentHome() {
|
|
285
|
+
return process.env.LITE_AGENT_HOME || join2(homedir(), ".lite-agent");
|
|
286
|
+
}
|
|
287
|
+
function projectHash(workdir) {
|
|
288
|
+
return createHash("sha1").update(resolve2(workdir)).digest("hex").slice(0, 16);
|
|
289
|
+
}
|
|
290
|
+
function resolveProjectPaths(opts) {
|
|
291
|
+
const home = opts.home ?? liteAgentHome();
|
|
292
|
+
const hash = projectHash(opts.workdir);
|
|
293
|
+
const projectDir = join2(home, "projects", hash);
|
|
294
|
+
return {
|
|
295
|
+
home,
|
|
296
|
+
hash,
|
|
297
|
+
spillDir: join2(projectDir, "spill"),
|
|
298
|
+
sessionsDir: join2(projectDir, "sessions"),
|
|
299
|
+
globalSkillsDir: join2(home, "skills"),
|
|
300
|
+
projectSkillsDir: join2(resolve2(opts.workdir), ".lite-agent", "skills")
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/store.ts
|
|
305
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
|
|
306
|
+
import { join as join3 } from "path";
|
|
307
|
+
function jsonlStore(opts) {
|
|
308
|
+
const fileFor = (id) => join3(opts.dir, `${id.replace(/[^a-zA-Z0-9_-]/g, "_")}.jsonl`);
|
|
309
|
+
return {
|
|
310
|
+
async load(id) {
|
|
311
|
+
const file = fileFor(id);
|
|
312
|
+
if (!existsSync2(file)) return null;
|
|
313
|
+
return readFileSync3(file, "utf8").split("\n").filter((line) => line.trim() !== "").map((line) => JSON.parse(line));
|
|
314
|
+
},
|
|
315
|
+
async save(id, messages) {
|
|
316
|
+
mkdirSync2(opts.dir, { recursive: true });
|
|
317
|
+
const body = messages.map((m) => JSON.stringify(m)).join("\n");
|
|
318
|
+
writeFileSync2(fileFor(id), messages.length ? body + "\n" : "");
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/spill.ts
|
|
324
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, readFileSync as readFileSync4, existsSync as existsSync3 } from "fs";
|
|
325
|
+
import { createHash as createHash2 } from "crypto";
|
|
326
|
+
import { join as join4 } from "path";
|
|
327
|
+
import { z as z6 } from "zod";
|
|
328
|
+
import { defineTool as defineTool6 } from "@lite-agent/core";
|
|
329
|
+
function fileSpillStore(opts) {
|
|
330
|
+
const fileFor = (ref) => join4(opts.dir, `${ref.replace(/[^a-f0-9]/gi, "")}.txt`);
|
|
331
|
+
return {
|
|
332
|
+
put(content) {
|
|
333
|
+
mkdirSync3(opts.dir, { recursive: true });
|
|
334
|
+
const ref = createHash2("sha1").update(content).digest("hex").slice(0, 16);
|
|
335
|
+
writeFileSync3(fileFor(ref), content);
|
|
336
|
+
return ref;
|
|
337
|
+
},
|
|
338
|
+
get(ref) {
|
|
339
|
+
const file = fileFor(ref);
|
|
340
|
+
return existsSync3(file) ? readFileSync4(file, "utf8") : null;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function readSpilledTool(store) {
|
|
345
|
+
return defineTool6({
|
|
346
|
+
name: "read_spilled",
|
|
347
|
+
description: "Retrieve the full content of a tool result that was moved off-context to save space. Pass the ref shown in its [spilled:<ref>] marker.",
|
|
348
|
+
schema: z6.object({ ref: z6.string() }),
|
|
349
|
+
execute: ({ ref }) => store.get(ref) ?? `No spilled content for ref '${ref}'`
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// src/cleanup.ts
|
|
354
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2, statSync, rmSync } from "fs";
|
|
355
|
+
import { join as join5 } from "path";
|
|
356
|
+
var DAY_MS = 864e5;
|
|
357
|
+
function sweepStale(opts = {}) {
|
|
358
|
+
const home = opts.home ?? liteAgentHome();
|
|
359
|
+
const cutoff = Date.now() - (opts.maxAgeDays ?? 30) * DAY_MS;
|
|
360
|
+
try {
|
|
361
|
+
const projectsDir = join5(home, "projects");
|
|
362
|
+
if (!existsSync4(projectsDir)) return;
|
|
363
|
+
for (const project of readdirSync2(projectsDir)) {
|
|
364
|
+
for (const sub of ["spill", "sessions"]) {
|
|
365
|
+
const dir = join5(projectsDir, project, sub);
|
|
366
|
+
if (!existsSync4(dir)) continue;
|
|
367
|
+
for (const name of readdirSync2(dir)) {
|
|
368
|
+
const fp = join5(dir, name);
|
|
369
|
+
try {
|
|
370
|
+
if (statSync(fp).mtimeMs < cutoff) rmSync(fp);
|
|
371
|
+
} catch {
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
} catch {
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
273
380
|
// src/createLiteAgent.ts
|
|
274
381
|
function createLiteAgent(cfg) {
|
|
382
|
+
const paths = resolveProjectPaths({ workdir: cfg.workdir, home: cfg.home });
|
|
383
|
+
if (cfg.cleanup !== false) {
|
|
384
|
+
sweepStale({
|
|
385
|
+
home: paths.home,
|
|
386
|
+
maxAgeDays: typeof cfg.cleanup === "object" ? cfg.cleanup.maxAgeDays : void 0
|
|
387
|
+
});
|
|
388
|
+
}
|
|
275
389
|
let tools = [...defaultTools(cfg.workdir)];
|
|
390
|
+
const loader = new SkillLoader([
|
|
391
|
+
paths.globalSkillsDir,
|
|
392
|
+
paths.projectSkillsDir,
|
|
393
|
+
...cfg.skillsDir ? [cfg.skillsDir] : []
|
|
394
|
+
]);
|
|
276
395
|
let skills = "(no skills available)";
|
|
277
|
-
if (
|
|
278
|
-
const loader = new SkillLoader(cfg.skillsDir);
|
|
396
|
+
if (loader.names().length > 0) {
|
|
279
397
|
tools.push(loadSkillTool(loader));
|
|
280
398
|
skills = loader.getDescriptions();
|
|
281
399
|
}
|
|
400
|
+
const spillEnabled = cfg.spill !== false;
|
|
401
|
+
const spillStore = spillEnabled ? fileSpillStore({ dir: paths.spillDir }) : void 0;
|
|
402
|
+
if (spillStore) tools.push(readSpilledTool(spillStore));
|
|
282
403
|
if (cfg.tools) tools.push(...cfg.tools);
|
|
283
404
|
if (cfg.onAskUser) tools.push(askUserTool());
|
|
284
405
|
if (cfg.allowedTools)
|
|
285
406
|
tools = tools.filter((t) => cfg.allowedTools.includes(t.name));
|
|
286
407
|
if (cfg.disallowedTools)
|
|
287
408
|
tools = tools.filter((t) => !cfg.disallowedTools.includes(t.name));
|
|
288
|
-
const system = cfg.system ?? buildSystemPrompt({
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
409
|
+
const system = cfg.system ?? buildSystemPrompt({ workdir: cfg.workdir, modelName: cfg.modelName, skills });
|
|
410
|
+
const compactor = cfg.compactor === false ? void 0 : cfg.compactor ?? defaultCompactor({
|
|
411
|
+
spillStore,
|
|
412
|
+
budgetBytes: typeof cfg.spill === "object" ? cfg.spill.budgetBytes : void 0
|
|
292
413
|
});
|
|
414
|
+
const store = cfg.store ?? (cfg.sessions === false ? void 0 : jsonlStore({ dir: paths.sessionsDir }));
|
|
293
415
|
const use = [
|
|
416
|
+
// proactive compaction (beforeModel) + reactive overflow net (wrapModelCall)
|
|
417
|
+
...compactor ? [compaction(compactor), reactiveCompaction()] : [],
|
|
294
418
|
...cfg.permission ? [permission(cfg.permission, cfg.onApproval)] : [],
|
|
295
419
|
...cfg.use ?? []
|
|
296
420
|
];
|
|
@@ -304,6 +428,7 @@ function createLiteAgent(cfg) {
|
|
|
304
428
|
maxTurns: cfg.maxTurns,
|
|
305
429
|
maxTokens: cfg.maxTokens,
|
|
306
430
|
sandbox: cfg.sandbox,
|
|
431
|
+
store,
|
|
307
432
|
input: cfg.onAskUser
|
|
308
433
|
});
|
|
309
434
|
}
|
|
@@ -323,6 +448,12 @@ function query(opts) {
|
|
|
323
448
|
maxTokens: opts.maxTokens,
|
|
324
449
|
use: opts.use,
|
|
325
450
|
sandbox: opts.sandbox,
|
|
451
|
+
store: opts.store,
|
|
452
|
+
compactor: opts.compactor,
|
|
453
|
+
home: opts.home,
|
|
454
|
+
sessions: opts.sessions,
|
|
455
|
+
spill: opts.spill,
|
|
456
|
+
cleanup: opts.cleanup,
|
|
326
457
|
permission: opts.permission,
|
|
327
458
|
onApproval: opts.onApproval,
|
|
328
459
|
onAskUser: opts.onAskUser
|
|
@@ -334,9 +465,9 @@ function query(opts) {
|
|
|
334
465
|
}
|
|
335
466
|
|
|
336
467
|
// src/tool.ts
|
|
337
|
-
import { defineTool as
|
|
468
|
+
import { defineTool as defineTool7 } from "@lite-agent/core";
|
|
338
469
|
function tool(name, description, schema, handler) {
|
|
339
|
-
return
|
|
470
|
+
return defineTool7({ name, description, schema, execute: handler });
|
|
340
471
|
}
|
|
341
472
|
export {
|
|
342
473
|
SkillLoader,
|
|
@@ -345,10 +476,17 @@ export {
|
|
|
345
476
|
buildSystemPrompt,
|
|
346
477
|
createLiteAgent,
|
|
347
478
|
defaultTools,
|
|
479
|
+
fileSpillStore,
|
|
348
480
|
fileTools,
|
|
481
|
+
jsonlStore,
|
|
482
|
+
liteAgentHome,
|
|
349
483
|
loadSkillTool,
|
|
350
484
|
makeSafePath,
|
|
485
|
+
projectHash,
|
|
351
486
|
query,
|
|
487
|
+
readSpilledTool,
|
|
488
|
+
resolveProjectPaths,
|
|
489
|
+
sweepStale,
|
|
352
490
|
todoTool,
|
|
353
491
|
tool
|
|
354
492
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lite-agent/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Batteries-included agent SDK over @lite-agent/core: tools, skills, system prompt, and query()/createLiteAgent().",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"type": "module",
|
|
@@ -34,19 +34,20 @@
|
|
|
34
34
|
"publishConfig": {
|
|
35
35
|
"access": "public"
|
|
36
36
|
},
|
|
37
|
-
"scripts": {
|
|
38
|
-
"build": "tsup src/index.ts --format esm --dts --clean --tsconfig tsconfig.build.json",
|
|
39
|
-
"test": "vitest run",
|
|
40
|
-
"typecheck": "tsc --noEmit"
|
|
41
|
-
},
|
|
42
37
|
"dependencies": {
|
|
43
|
-
"
|
|
44
|
-
"zod": "^4.3.6"
|
|
38
|
+
"gray-matter": "^4.0.3",
|
|
39
|
+
"zod": "^4.3.6",
|
|
40
|
+
"@lite-agent/core": "0.2.0"
|
|
45
41
|
},
|
|
46
42
|
"devDependencies": {
|
|
47
43
|
"@types/node": "^25.5.0",
|
|
48
44
|
"tsup": "^8.3.0",
|
|
49
45
|
"typescript": "^6.0.2",
|
|
50
46
|
"vitest": "^2.1.0"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsup src/index.ts --format esm --dts --clean --tsconfig tsconfig.build.json",
|
|
50
|
+
"test": "vitest run",
|
|
51
|
+
"typecheck": "tsc --noEmit"
|
|
51
52
|
}
|
|
52
|
-
}
|
|
53
|
+
}
|