@kweaver-ai/kweaver-sdk 0.7.2 → 0.7.4

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 (69) hide show
  1. package/README.md +35 -1
  2. package/README.zh.md +26 -0
  3. package/bin/kweaver.js +12 -11
  4. package/dist/api/bkn-backend.d.ts +1 -0
  5. package/dist/api/bkn-backend.js +1 -1
  6. package/dist/api/bkn-metrics.d.ts +59 -0
  7. package/dist/api/bkn-metrics.js +129 -0
  8. package/dist/api/conversations.d.ts +47 -2
  9. package/dist/api/conversations.js +113 -17
  10. package/dist/api/datasources.d.ts +7 -0
  11. package/dist/api/datasources.js +51 -6
  12. package/dist/api/model-invocation.d.ts +58 -0
  13. package/dist/api/model-invocation.js +203 -0
  14. package/dist/api/models.d.ts +79 -0
  15. package/dist/api/models.js +183 -0
  16. package/dist/api/ontology-query-metrics.d.ts +14 -0
  17. package/dist/api/ontology-query-metrics.js +30 -0
  18. package/dist/api/toolboxes.d.ts +2 -0
  19. package/dist/api/toolboxes.js +2 -1
  20. package/dist/bundled-model-templates.d.ts +17 -0
  21. package/dist/bundled-model-templates.js +24 -0
  22. package/dist/cli.js +28 -2
  23. package/dist/client.d.ts +3 -0
  24. package/dist/client.js +5 -0
  25. package/dist/commands/agent.d.ts +7 -1
  26. package/dist/commands/agent.js +75 -21
  27. package/dist/commands/auth.js +42 -7
  28. package/dist/commands/bkn-metric.d.ts +1 -0
  29. package/dist/commands/bkn-metric.js +406 -0
  30. package/dist/commands/bkn-ops.d.ts +2 -1
  31. package/dist/commands/bkn-ops.js +75 -34
  32. package/dist/commands/bkn-utils.d.ts +55 -2
  33. package/dist/commands/bkn-utils.js +103 -9
  34. package/dist/commands/bkn.js +4 -0
  35. package/dist/commands/dataflow.js +194 -20
  36. package/dist/commands/ds.d.ts +0 -1
  37. package/dist/commands/ds.js +26 -10
  38. package/dist/commands/explore-chat.js +2 -2
  39. package/dist/commands/import-csv.d.ts +0 -2
  40. package/dist/commands/import-csv.js +2 -4
  41. package/dist/commands/model.d.ts +72 -0
  42. package/dist/commands/model.js +1315 -0
  43. package/dist/commands/tool.d.ts +1 -0
  44. package/dist/commands/tool.js +12 -0
  45. package/dist/config/store.d.ts +1 -0
  46. package/dist/config/store.js +17 -0
  47. package/dist/index.d.ts +9 -0
  48. package/dist/index.js +5 -0
  49. package/dist/resources/models.d.ts +40 -0
  50. package/dist/resources/models.js +88 -0
  51. package/dist/resources/toolboxes.d.ts +2 -0
  52. package/dist/templates/bkn/document/manifest.json +12 -0
  53. package/dist/templates/bkn/document/template.json +757 -0
  54. package/dist/templates/dataflow/unstructured/manifest.json +11 -0
  55. package/dist/templates/dataflow/unstructured/template.json +63 -0
  56. package/dist/templates/dataset/document/manifest.json +10 -0
  57. package/dist/templates/dataset/document/template.json +23 -0
  58. package/dist/templates/dataset/document-content/manifest.json +10 -0
  59. package/dist/templates/dataset/document-content/template.json +29 -0
  60. package/dist/templates/dataset/document-element/manifest.json +10 -0
  61. package/dist/templates/dataset/document-element/template.json +21 -0
  62. package/dist/templates/model/llm-basic.json +13 -0
  63. package/dist/templates/model/manifest.json +16 -0
  64. package/dist/templates/model/small-basic.json +6 -0
  65. package/dist/utils/template-loader.d.ts +40 -0
  66. package/dist/utils/template-loader.js +129 -0
  67. package/dist/utils/trace-views.d.ts +44 -0
  68. package/dist/utils/trace-views.js +425 -0
  69. package/package.json +3 -3
@@ -0,0 +1,425 @@
1
+ /**
2
+ * Build a parent-child forest from a flat span array.
3
+ *
4
+ * Spans whose parentSpanId is missing or points to a span that is not in the set
5
+ * are treated as roots. Multiple traceIds in one conversation produce multiple roots.
6
+ * Children are sorted by startTime ascending.
7
+ */
8
+ export function buildSpanTree(spans) {
9
+ const byId = new Map();
10
+ for (const span of spans) {
11
+ byId.set(span.spanId, { span, children: [] });
12
+ }
13
+ const roots = [];
14
+ for (const node of byId.values()) {
15
+ const parentId = node.span.parentSpanId;
16
+ const parent = parentId ? byId.get(parentId) : undefined;
17
+ if (parent) {
18
+ parent.children.push(node);
19
+ }
20
+ else {
21
+ roots.push(node);
22
+ }
23
+ }
24
+ const sortByStart = (a, b) => {
25
+ const ta = String(a.span.startTime);
26
+ const tb = String(b.span.startTime);
27
+ return ta < tb ? -1 : ta > tb ? 1 : 0;
28
+ };
29
+ const sortRecursive = (nodes) => {
30
+ nodes.sort(sortByStart);
31
+ for (const n of nodes)
32
+ sortRecursive(n.children);
33
+ };
34
+ sortRecursive(roots);
35
+ return roots;
36
+ }
37
+ function durationMs(span) {
38
+ if (typeof span.durationInNanos === "number")
39
+ return span.durationInNanos / 1e6;
40
+ return 0;
41
+ }
42
+ function formatMs(ms) {
43
+ if (ms >= 1000)
44
+ return `${ms.toFixed(1)}ms`;
45
+ if (ms >= 10)
46
+ return `${ms.toFixed(1)}ms`;
47
+ return `${ms.toFixed(2)}ms`;
48
+ }
49
+ function statusLabel(span) {
50
+ const code = span.status?.code;
51
+ if (code === undefined || code === null || code === "")
52
+ return "Uns";
53
+ const s = String(code).toUpperCase();
54
+ if (s === "OK" || s === "1")
55
+ return "Ok";
56
+ if (s === "ERROR" || s === "2")
57
+ return "Err";
58
+ if (s === "UNSET" || s === "0")
59
+ return "Uns";
60
+ return s.slice(0, 3);
61
+ }
62
+ function attr(span, ...keys) {
63
+ if (!span.attributes)
64
+ return undefined;
65
+ for (const key of keys) {
66
+ const v = span.attributes[key];
67
+ if (v !== undefined && v !== null)
68
+ return v;
69
+ }
70
+ return undefined;
71
+ }
72
+ const TOOL_NAME_KEYS = ["gen_ai.tool.name", "tool.name"];
73
+ const TOOL_ARGS_KEYS = ["gen_ai.tool.call.arguments", "gen_ai.tool.arguments", "tool.arguments"];
74
+ const TOOL_RESULT_KEYS = ["gen_ai.tool.call.result", "gen_ai.tool.result", "tool.result", "tool.output"];
75
+ const LLM_MODEL_KEYS = ["gen_ai.request.model", "llm.model"];
76
+ const LLM_IN_TOK_KEYS = ["gen_ai.usage.input_tokens", "llm.usage.input_tokens"];
77
+ const LLM_OUT_TOK_KEYS = ["gen_ai.usage.output_tokens", "llm.usage.output_tokens"];
78
+ const LLM_FINISH_KEYS = ["gen_ai.response.finish_reasons", "llm.response.finish_reason"];
79
+ function spanTooltip(span) {
80
+ const parts = [];
81
+ const toolName = attr(span, ...TOOL_NAME_KEYS);
82
+ if (toolName)
83
+ parts.push(`tool=${String(toolName)}`);
84
+ const model = attr(span, ...LLM_MODEL_KEYS);
85
+ if (model)
86
+ parts.push(`model=${String(model)}`);
87
+ const inTok = attr(span, ...LLM_IN_TOK_KEYS);
88
+ if (inTok)
89
+ parts.push(`in=${String(inTok)}`);
90
+ const outTok = attr(span, ...LLM_OUT_TOK_KEYS);
91
+ if (outTok)
92
+ parts.push(`out=${String(outTok)}`);
93
+ const args = attr(span, ...TOOL_ARGS_KEYS);
94
+ if (typeof args === "string" && args.length > 0) {
95
+ const truncated = args.length > 80 ? args.slice(0, 77) + "..." : args;
96
+ parts.push(`args=${truncated}`);
97
+ }
98
+ return parts.length ? ` · ${parts.join(" · ")}` : "";
99
+ }
100
+ /**
101
+ * Tree view: indented call topology with duration, status, service, and inline metadata.
102
+ */
103
+ export function formatTreeView(spans) {
104
+ if (spans.length === 0)
105
+ return "(no spans)";
106
+ const roots = buildSpanTree(spans);
107
+ const lines = [];
108
+ const walk = (node, prefix, isLast, isRoot) => {
109
+ const branch = isRoot ? "" : isLast ? " └ " : " ├ ";
110
+ const dur = formatMs(durationMs(node.span));
111
+ const status = statusLabel(node.span);
112
+ const service = node.span.serviceName ? ` [${node.span.serviceName}]` : "";
113
+ const padded = `${prefix}${branch}${node.span.name}`.padEnd(60, " ");
114
+ lines.push(`${padded} ${dur.padStart(10, " ")} ${status.padEnd(3, " ")}${service}${spanTooltip(node.span)}`);
115
+ const childPrefix = isRoot ? "" : prefix + (isLast ? " " : " │ ");
116
+ for (let i = 0; i < node.children.length; i += 1) {
117
+ walk(node.children[i], childPrefix, i === node.children.length - 1, false);
118
+ }
119
+ };
120
+ for (const root of roots)
121
+ walk(root, "", true, true);
122
+ return lines.join("\n");
123
+ }
124
+ function classify(span) {
125
+ const name = span.name.toLowerCase();
126
+ if (name.startsWith("chat") || attr(span, ...LLM_MODEL_KEYS)) {
127
+ return "LLM (chat)";
128
+ }
129
+ if (name.startsWith("execute_tool")) {
130
+ const tool = attr(span, ...TOOL_NAME_KEYS);
131
+ return `tool:${tool ? String(tool) : name.replace(/^execute_tool\s*/, "")}`;
132
+ }
133
+ if (name.endsWith("_prompt"))
134
+ return "prompt-build";
135
+ if (name.includes("repo") ||
136
+ attr(span, "db.system") ||
137
+ attr(span, "db.statement")) {
138
+ return "db";
139
+ }
140
+ return "other";
141
+ }
142
+ /**
143
+ * Perf view: aggregated duration and call count per category.
144
+ */
145
+ export function formatPerfView(spans) {
146
+ if (spans.length === 0)
147
+ return "(no spans)";
148
+ const buckets = new Map();
149
+ for (const span of spans) {
150
+ const key = classify(span);
151
+ const bucket = buckets.get(key) ?? { total: 0, count: 0 };
152
+ bucket.total += durationMs(span);
153
+ bucket.count += 1;
154
+ buckets.set(key, bucket);
155
+ }
156
+ const rows = [...buckets.entries()].sort((a, b) => b[1].total - a[1].total);
157
+ const lines = [];
158
+ lines.push(`${"类别".padEnd(28, " ")} ${"累计耗时".padStart(12, " ")} ${"次数".padStart(6, " ")}`);
159
+ for (const [name, b] of rows) {
160
+ lines.push(`${name.padEnd(28, " ")} ${(b.total.toFixed(0) + "ms").padStart(12, " ")} ${String(b.count).padStart(6, " ")}`);
161
+ }
162
+ return lines.join("\n");
163
+ }
164
+ function parseJsonish(value) {
165
+ if (typeof value !== "string" || value.length === 0)
166
+ return null;
167
+ try {
168
+ const parsed = JSON.parse(value);
169
+ return typeof parsed === "object" && parsed !== null ? parsed : null;
170
+ }
171
+ catch {
172
+ return null;
173
+ }
174
+ }
175
+ function extractHits(span) {
176
+ // trace-ai wraps the tool result in {"answer": "<stringified-json>", "block_answer": "..."}.
177
+ // Unwrap one layer if present, then look for hits/datas/results array.
178
+ const raw = attr(span, ...TOOL_RESULT_KEYS);
179
+ let parsed = parseJsonish(raw);
180
+ if (parsed && typeof parsed.answer === "string") {
181
+ const unwrapped = parseJsonish(parsed.answer);
182
+ if (unwrapped)
183
+ parsed = unwrapped;
184
+ }
185
+ if (!parsed)
186
+ return [];
187
+ const candidates = [];
188
+ if (Array.isArray(parsed))
189
+ candidates.push(...parsed);
190
+ else if (Array.isArray(parsed.hits))
191
+ candidates.push(...parsed.hits);
192
+ else if (Array.isArray(parsed.datas))
193
+ candidates.push(...parsed.datas);
194
+ else if (Array.isArray(parsed.data))
195
+ candidates.push(...parsed.data);
196
+ else if (Array.isArray(parsed.results))
197
+ candidates.push(...parsed.results);
198
+ else if (Array.isArray(parsed.entries))
199
+ candidates.push(...parsed.entries);
200
+ const hits = [];
201
+ for (const c of candidates) {
202
+ if (typeof c !== "object" || c === null)
203
+ continue;
204
+ const obj = c;
205
+ const score = typeof obj._score === "number" ? obj._score : undefined;
206
+ const previewKeys = ["name", "title", "label", "skill_id", "_instance_id", "id"];
207
+ let preview;
208
+ for (const k of previewKeys) {
209
+ if (typeof obj[k] === "string") {
210
+ preview = `${k}=${obj[k]}`;
211
+ break;
212
+ }
213
+ }
214
+ hits.push({ score, preview });
215
+ }
216
+ return hits;
217
+ }
218
+ /**
219
+ * Evidence view: ordered chain of execute_tool spans with arguments, hit count, and score.
220
+ */
221
+ export function formatEvidenceView(spans) {
222
+ const tools = spans
223
+ .filter((s) => s.name.toLowerCase().startsWith("execute_tool"))
224
+ .sort((a, b) => String(a.startTime).localeCompare(String(b.startTime)));
225
+ const llms = spans.filter((s) => {
226
+ const cls = classify(s);
227
+ return cls === "LLM (chat)";
228
+ });
229
+ if (tools.length === 0 && llms.length === 0)
230
+ return "(no tool / llm spans)";
231
+ const lines = [];
232
+ lines.push(`${"步".padEnd(4)} ${"工具".padEnd(26)} ${"耗时".padStart(8)} 详情`);
233
+ let step = 1;
234
+ for (const span of tools) {
235
+ const toolName = String(attr(span, ...TOOL_NAME_KEYS) ?? span.name);
236
+ const args = attr(span, ...TOOL_ARGS_KEYS);
237
+ const argText = typeof args === "string" && args.length > 0 ? args : "";
238
+ const truncated = argText.length > 100 ? argText.slice(0, 97) + "..." : argText;
239
+ const dur = formatMs(durationMs(span)).padStart(8);
240
+ lines.push(`${String(step).padEnd(4)} ${toolName.padEnd(26)} ${dur} ${truncated}`);
241
+ const hits = extractHits(span);
242
+ if (hits.length > 0) {
243
+ const previews = hits
244
+ .slice(0, 3)
245
+ .map((h) => {
246
+ const parts = [];
247
+ if (h.preview)
248
+ parts.push(h.preview);
249
+ if (typeof h.score === "number")
250
+ parts.push(`_score=${h.score.toFixed(3)}`);
251
+ return parts.join(", ");
252
+ })
253
+ .filter((s) => s.length > 0);
254
+ const more = hits.length > previews.length ? `, +${hits.length - previews.length} more` : "";
255
+ const prefix = " └ ";
256
+ lines.push(`${prefix}命中 ${hits.length} 条${previews.length ? ": " + previews.join(" | ") + more : ""}`);
257
+ }
258
+ step += 1;
259
+ }
260
+ for (const span of llms) {
261
+ const model = attr(span, ...LLM_MODEL_KEYS) ?? "?";
262
+ const inTok = attr(span, ...LLM_IN_TOK_KEYS) ?? "?";
263
+ const outTok = attr(span, ...LLM_OUT_TOK_KEYS) ?? "?";
264
+ const finish = attr(span, ...LLM_FINISH_KEYS) ?? "?";
265
+ const dur = formatMs(durationMs(span));
266
+ lines.push(`LLM: ${String(model)} · in=${String(inTok)} tok · out=${String(outTok)} tok · ${dur} · finish=${String(finish)}`);
267
+ }
268
+ return lines.join("\n");
269
+ }
270
+ function parseChatMessages(raw) {
271
+ if (typeof raw !== "string")
272
+ return [];
273
+ try {
274
+ const parsed = JSON.parse(raw);
275
+ if (!Array.isArray(parsed))
276
+ return [];
277
+ return parsed;
278
+ }
279
+ catch {
280
+ return [];
281
+ }
282
+ }
283
+ function extractLlmMessages(span) {
284
+ const events = span.raw?.events ?? span.events;
285
+ const empty = { input: [], output: [] };
286
+ if (!Array.isArray(events))
287
+ return empty;
288
+ for (const event of events) {
289
+ const attrs = event.attributes;
290
+ if (!attrs)
291
+ continue;
292
+ const inRaw = attrs["gen_ai.input.messages"];
293
+ const outRaw = attrs["gen_ai.output.messages"];
294
+ if (inRaw || outRaw) {
295
+ return { input: parseChatMessages(inRaw), output: parseChatMessages(outRaw) };
296
+ }
297
+ }
298
+ return empty;
299
+ }
300
+ function truncate(text, max) {
301
+ if (text.length <= max)
302
+ return text;
303
+ return text.slice(0, max - 3) + "...";
304
+ }
305
+ function indent(text, prefix) {
306
+ return text.split("\n").map((l) => prefix + l).join("\n");
307
+ }
308
+ function unwrapToolAnswer(raw) {
309
+ // trace-ai tool messages look like: "{'answer': '<stringified-json>', 'block_answer': ...}".
310
+ // Python repr (single-quoted) is not valid JSON, so try a forgiving parse before showing raw.
311
+ const directParsed = parseJsonish(raw);
312
+ if (directParsed && typeof directParsed.answer === "string")
313
+ return directParsed.answer;
314
+ // Fallback: replace single quotes with double quotes for a best-effort view.
315
+ return raw;
316
+ }
317
+ /**
318
+ * Reasoning view: walks the LLM's chat history (input messages of the chat span +
319
+ * the assistant's final output) so the user can read the agent's actual thought
320
+ * process — system / user / assistant / tool_call / tool result / final answer.
321
+ */
322
+ export function formatReasoningView(spans, opts = {}) {
323
+ const limits = opts.full
324
+ ? { system: Infinity, user: Infinity, assistant: Infinity, tool: Infinity, toolCall: Infinity }
325
+ : { system: 400, user: 600, assistant: 500, tool: 400, toolCall: 200 };
326
+ // Pick the deepest chat span — usually only one per turn, but be defensive.
327
+ const chatSpans = spans.filter((s) => {
328
+ const cls = classify(s);
329
+ return cls === "LLM (chat)";
330
+ });
331
+ if (chatSpans.length === 0)
332
+ return "(no chat span found — this conversation never reached the LLM)";
333
+ // Pick the chat span with the largest input — typically the final reasoning step.
334
+ let target = chatSpans[0];
335
+ let targetSize = 0;
336
+ for (const s of chatSpans) {
337
+ const ev = s.raw?.events?.[0];
338
+ const size = ev?.attributes?.["gen_ai.input.messages"];
339
+ const len = typeof size === "string" ? size.length : 0;
340
+ if (len > targetSize) {
341
+ targetSize = len;
342
+ target = s;
343
+ }
344
+ }
345
+ const { input, output } = extractLlmMessages(target);
346
+ if (input.length === 0 && output.length === 0) {
347
+ return "(chat span found but it carries no gen_ai.input.messages / gen_ai.output.messages event)";
348
+ }
349
+ const lines = [];
350
+ const model = String(attr(target, ...LLM_MODEL_KEYS) ?? "?");
351
+ const inTok = String(attr(target, ...LLM_IN_TOK_KEYS) ?? "?");
352
+ const outTok = String(attr(target, ...LLM_OUT_TOK_KEYS) ?? "?");
353
+ lines.push(`LLM: ${model} · in=${inTok} tok · out=${outTok} tok · ${input.length} input messages\n`);
354
+ for (let i = 0; i < input.length; i += 1) {
355
+ const m = input[i];
356
+ const role = m.role || "?";
357
+ if (role === "system") {
358
+ const content = typeof m.content === "string" ? m.content : "";
359
+ lines.push(`[${i}] system · ${content.length} chars`);
360
+ lines.push(indent(truncate(content, limits.system), " "));
361
+ }
362
+ else if (role === "user") {
363
+ const content = typeof m.content === "string" ? m.content : "";
364
+ lines.push(`[${i}] user`);
365
+ lines.push(indent(truncate(content, limits.user), " "));
366
+ }
367
+ else if (role === "assistant") {
368
+ const content = typeof m.content === "string" ? m.content : "";
369
+ lines.push(`[${i}] assistant`);
370
+ if (content.length > 0) {
371
+ lines.push(indent(truncate(content, limits.assistant), " "));
372
+ }
373
+ const calls = m.tool_calls ?? [];
374
+ for (const call of calls) {
375
+ const fn = call.function ?? {};
376
+ const args = String(fn.arguments ?? "");
377
+ lines.push(` → tool_call ${fn.name ?? "?"}(${truncate(args, limits.toolCall)})`);
378
+ }
379
+ }
380
+ else if (role === "tool") {
381
+ const name = m.name ?? "tool";
382
+ const content = typeof m.content === "string" ? m.content : "";
383
+ const unwrapped = unwrapToolAnswer(content);
384
+ lines.push(`[${i}] tool · ${name}`);
385
+ lines.push(indent(truncate(unwrapped, limits.tool), " "));
386
+ }
387
+ else {
388
+ const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content ?? "");
389
+ lines.push(`[${i}] ${role}`);
390
+ lines.push(indent(truncate(content, Math.min(limits.tool, 300)), " "));
391
+ }
392
+ lines.push("");
393
+ }
394
+ if (output.length > 0) {
395
+ lines.push("─── Final answer ───");
396
+ for (const m of output) {
397
+ const content = typeof m.content === "string" ? m.content : "";
398
+ if (content.length > 0)
399
+ lines.push(content);
400
+ }
401
+ }
402
+ return lines.join("\n");
403
+ }
404
+ /**
405
+ * Render one or more views over a trace result. Returns concatenated text suitable for stdout.
406
+ */
407
+ export function formatTraceResult(result, view, opts = {}) {
408
+ if (result.spans.length === 0) {
409
+ return `(no spans for conversation ${result.conversationId})`;
410
+ }
411
+ const sections = [];
412
+ if (view === "tree" || view === "all")
413
+ sections.push(["── Tree ──", () => formatTreeView(result.spans)]);
414
+ if (view === "perf" || view === "all")
415
+ sections.push(["── Perf ──", () => formatPerfView(result.spans)]);
416
+ if (view === "evidence" || view === "all")
417
+ sections.push(["── Evidence ──", () => formatEvidenceView(result.spans)]);
418
+ if (view === "reasoning" || view === "all")
419
+ sections.push(["── Reasoning ──", () => formatReasoningView(result.spans, { full: opts.full })]);
420
+ const head = result.truncated
421
+ ? `[warn] traceId aggregation truncated — increase maxTraceIds for full coverage\n`
422
+ : "";
423
+ const body = sections.map(([title, fn]) => `${title}\n${fn()}`).join("\n\n");
424
+ return head + body;
425
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kweaver-ai/kweaver-sdk",
3
- "version": "0.7.2",
3
+ "version": "0.7.4",
4
4
  "description": "KWeaver TypeScript SDK — CLI tool and programmatic API for knowledge networks and Decision Agents.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -24,9 +24,9 @@
24
24
  ],
25
25
  "scripts": {
26
26
  "dev": "tsx watch src/cli.ts",
27
- "build": "tsc -p tsconfig.json && (cp -r src/templates dist/ 2>/dev/null || true)",
27
+ "build": "node node_modules/typescript/bin/tsc -p tsconfig.json && rm -rf dist/templates && cp -R src/templates dist/templates",
28
28
  "start": "node ./dist/cli.js",
29
- "lint": "tsc --noEmit -p tsconfig.json",
29
+ "lint": "node node_modules/typescript/bin/tsc --noEmit -p tsconfig.json",
30
30
  "test": "node --import tsx --test test/*.test.ts",
31
31
  "test:e2e": "node --import tsx --test test/e2e/*.test.ts",
32
32
  "prepublishOnly": "npm run build"