pi-devin-fusion 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/README.md +130 -0
- package/package.json +44 -0
- package/src/config.ts +73 -0
- package/src/context.ts +86 -0
- package/src/executor.ts +127 -0
- package/src/executor_policy.ts +38 -0
- package/src/index.ts +509 -0
- package/src/llm.ts +248 -0
- package/src/models.ts +53 -0
- package/src/prompts.ts +23 -0
- package/src/tools.ts +108 -0
- package/src/types.ts +70 -0
- package/src/ui.ts +370 -0
- package/src/utils.ts +71 -0
- package/tsconfig.json +16 -0
package/src/ui.ts
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native pi TUI setup for pi-devin-fusion.
|
|
3
|
+
*
|
|
4
|
+
* Interactive-only session setup: choose one executor model (or auto) and
|
|
5
|
+
* session-scoped executor config without writing .pi/devin.json.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import { DynamicBorder, getSelectListTheme } from "@earendil-works/pi-coding-agent";
|
|
10
|
+
import {
|
|
11
|
+
Container,
|
|
12
|
+
Input,
|
|
13
|
+
Key,
|
|
14
|
+
matchesKey,
|
|
15
|
+
type SelectItem,
|
|
16
|
+
SelectList,
|
|
17
|
+
Spacer,
|
|
18
|
+
Text,
|
|
19
|
+
} from "@earendil-works/pi-tui";
|
|
20
|
+
import { modelDisplay } from "./models.ts";
|
|
21
|
+
import { clampMaxToolCalls, isMutatingSelection } from "./tools.ts";
|
|
22
|
+
import type { Api, DevinMode, FooterDisplay, Model, ToolMode } from "./types.ts";
|
|
23
|
+
|
|
24
|
+
const TOOL_MODE_CYCLE: ToolMode[] = ["none", "readonly", "all"];
|
|
25
|
+
const FOOTER_DISPLAY_CYCLE: FooterDisplay[] = ["full", "compact", "off"];
|
|
26
|
+
const MAX_CALLS_PRESETS = [4, 8, 12, 16, 25, 50, 100];
|
|
27
|
+
|
|
28
|
+
interface ModelInfo {
|
|
29
|
+
identifier: string;
|
|
30
|
+
provider: string;
|
|
31
|
+
name: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface DevinSetupState {
|
|
35
|
+
executorId?: string;
|
|
36
|
+
executorAuto?: boolean;
|
|
37
|
+
mode?: DevinMode;
|
|
38
|
+
executorTools?: ToolMode;
|
|
39
|
+
maxToolCalls?: number;
|
|
40
|
+
toolsConsented?: boolean;
|
|
41
|
+
footerDisplay?: FooterDisplay;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function toModelInfo(available: Model<Api>[]): ModelInfo[] {
|
|
45
|
+
return available.map((m) => ({
|
|
46
|
+
identifier: modelDisplay(m),
|
|
47
|
+
provider: m.provider,
|
|
48
|
+
name: m.name,
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function filterModels(models: ModelInfo[], query: string): ModelInfo[] {
|
|
53
|
+
const trimmed = query.trim().toLowerCase();
|
|
54
|
+
if (!trimmed) return models;
|
|
55
|
+
return models.filter(
|
|
56
|
+
(m) =>
|
|
57
|
+
m.name.toLowerCase().includes(trimmed) ||
|
|
58
|
+
m.provider.toLowerCase().includes(trimmed) ||
|
|
59
|
+
m.identifier.toLowerCase().includes(trimmed),
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Toggle the single executor selection. Selecting the same model clears to auto. */
|
|
64
|
+
export function toggleExecutorSelection(current: string | undefined, id: string): string | undefined {
|
|
65
|
+
return current === id ? undefined : id;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Badge shown in the model picker's right column. */
|
|
69
|
+
export function executorBadge(isExecutor: boolean): string {
|
|
70
|
+
return isExecutor ? "◆ executor" : "";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Replace a SelectList's items in place. pi-tui exposes no public setItems(),
|
|
75
|
+
* and setFilter only prefix-matches value, so the picker needs direct guarded
|
|
76
|
+
* replacement for multi-field search and live badge relabels.
|
|
77
|
+
*/
|
|
78
|
+
function setSelectListItems(list: SelectList, items: SelectItem[]): void {
|
|
79
|
+
const internal = list as unknown as { items?: unknown; filteredItems?: unknown };
|
|
80
|
+
if (!Array.isArray(internal.items) || !Array.isArray(internal.filteredItems)) {
|
|
81
|
+
throw new Error("pi-tui SelectList internals changed; devin-setup needs a public setItems()");
|
|
82
|
+
}
|
|
83
|
+
internal.items = items;
|
|
84
|
+
internal.filteredItems = [...items];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Devin setup UI: two sections, Models + Config.
|
|
89
|
+
*
|
|
90
|
+
* Models: ↑/↓ navigate · e choose executor · x auto · / search
|
|
91
|
+
* Config: ↑/↓ move settings · Space or ←/→ change value
|
|
92
|
+
* Global: Tab switches section · Enter saves · Esc cancels.
|
|
93
|
+
*/
|
|
94
|
+
export async function selectDevinSetup(
|
|
95
|
+
ctx: ExtensionContext,
|
|
96
|
+
available: Model<Api>[],
|
|
97
|
+
initial: DevinSetupState,
|
|
98
|
+
): Promise<DevinSetupState | null> {
|
|
99
|
+
if (!ctx.hasUI) return null;
|
|
100
|
+
|
|
101
|
+
const models = toModelInfo(available);
|
|
102
|
+
const nameById = new Map(models.map((m) => [m.identifier, m.name] as const));
|
|
103
|
+
const state: DevinSetupState = {
|
|
104
|
+
executorId: initial.executorId,
|
|
105
|
+
executorAuto: initial.executorAuto ?? !initial.executorId,
|
|
106
|
+
mode: initial.mode ?? "available",
|
|
107
|
+
executorTools: initial.executorTools ?? "all",
|
|
108
|
+
maxToolCalls: clampMaxToolCalls(initial.maxToolCalls),
|
|
109
|
+
toolsConsented: initial.toolsConsented ?? false,
|
|
110
|
+
footerDisplay: initial.footerDisplay ?? "full",
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
interface ConfigRow {
|
|
114
|
+
label: string;
|
|
115
|
+
values: string[];
|
|
116
|
+
get: () => string;
|
|
117
|
+
set: (value: string) => void;
|
|
118
|
+
note: () => string;
|
|
119
|
+
}
|
|
120
|
+
const configRows: ConfigRow[] = [
|
|
121
|
+
{
|
|
122
|
+
label: "Executor tools",
|
|
123
|
+
values: TOOL_MODE_CYCLE,
|
|
124
|
+
get: () => state.executorTools ?? "all",
|
|
125
|
+
set: (v) => {
|
|
126
|
+
state.executorTools = v as ToolMode;
|
|
127
|
+
if (!isMutatingSelection(state.executorTools)) state.toolsConsented = false;
|
|
128
|
+
},
|
|
129
|
+
note: () =>
|
|
130
|
+
isMutatingSelection(state.executorTools)
|
|
131
|
+
? "'all' adds bash/edit/write — you'll confirm on save; mutating runs serialize"
|
|
132
|
+
: state.executorTools === "readonly"
|
|
133
|
+
? "read/grep/find/ls — sidekick can inspect the project"
|
|
134
|
+
: "sidekick answers without tools",
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
label: "Max tool calls",
|
|
138
|
+
values: MAX_CALLS_PRESETS.map(String),
|
|
139
|
+
get: () => String(clampMaxToolCalls(state.maxToolCalls)),
|
|
140
|
+
set: (v) => {
|
|
141
|
+
state.maxToolCalls = Number(v);
|
|
142
|
+
},
|
|
143
|
+
note: () => "max tool steps for each sidekick run when tools are on",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
label: "Footer",
|
|
147
|
+
values: FOOTER_DISPLAY_CYCLE,
|
|
148
|
+
get: () => state.footerDisplay ?? "full",
|
|
149
|
+
set: (v) => {
|
|
150
|
+
state.footerDisplay = v as FooterDisplay;
|
|
151
|
+
},
|
|
152
|
+
note: () =>
|
|
153
|
+
state.footerDisplay === "off"
|
|
154
|
+
? "restore Pi's built-in footer"
|
|
155
|
+
: state.footerDisplay === "compact"
|
|
156
|
+
? "show Devin mode and whether executor is set"
|
|
157
|
+
: "show Devin mode, executor, and config",
|
|
158
|
+
},
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
return ctx.ui.custom<DevinSetupState | null>((tui, theme, _kb, done) => {
|
|
162
|
+
let focus: "models" | "config" = "models";
|
|
163
|
+
let searching = false;
|
|
164
|
+
let query = "";
|
|
165
|
+
let configIndex = 0;
|
|
166
|
+
const searchBuffer = new Input();
|
|
167
|
+
|
|
168
|
+
const accent = (s: string) => theme.fg("accent", s);
|
|
169
|
+
const dim = (s: string) => theme.fg("dim", s);
|
|
170
|
+
|
|
171
|
+
const container = new Container();
|
|
172
|
+
container.addChild(new DynamicBorder((s) => accent(s)));
|
|
173
|
+
container.addChild(new Text(accent(theme.bold("Devin Setup"))));
|
|
174
|
+
container.addChild(new Text(dim("Choose one executor model, or clear to auto. Tab to Config.")));
|
|
175
|
+
container.addChild(new Spacer(1));
|
|
176
|
+
|
|
177
|
+
const executorLine = new Text("");
|
|
178
|
+
container.addChild(executorLine);
|
|
179
|
+
container.addChild(new Spacer(1));
|
|
180
|
+
|
|
181
|
+
const modelsHeader = new Text("");
|
|
182
|
+
const searchLine = new Text("");
|
|
183
|
+
container.addChild(modelsHeader);
|
|
184
|
+
container.addChild(searchLine);
|
|
185
|
+
|
|
186
|
+
const providerWidth = Math.min(16, Math.max(8, ...models.map((m) => m.provider.length)) + 2);
|
|
187
|
+
const makeItems = (filtered: ModelInfo[]): SelectItem[] =>
|
|
188
|
+
filtered.map((m) => ({
|
|
189
|
+
value: m.identifier,
|
|
190
|
+
label: `${m.provider.padEnd(providerWidth)}${m.name}`,
|
|
191
|
+
description: executorBadge(state.executorId === m.identifier),
|
|
192
|
+
}));
|
|
193
|
+
|
|
194
|
+
const selectList = new SelectList(
|
|
195
|
+
makeItems(models),
|
|
196
|
+
Math.min(Math.max(models.length, 1), 10),
|
|
197
|
+
getSelectListTheme(),
|
|
198
|
+
{ minPrimaryColumnWidth: providerWidth + 18, maxPrimaryColumnWidth: providerWidth + 40 },
|
|
199
|
+
);
|
|
200
|
+
container.addChild(selectList);
|
|
201
|
+
container.addChild(new Spacer(1));
|
|
202
|
+
|
|
203
|
+
const configHeader = new Text("");
|
|
204
|
+
container.addChild(configHeader);
|
|
205
|
+
const configTexts = configRows.map(() => new Text(""));
|
|
206
|
+
for (const t of configTexts) container.addChild(t);
|
|
207
|
+
|
|
208
|
+
const hint = new Text("");
|
|
209
|
+
container.addChild(hint);
|
|
210
|
+
container.addChild(new DynamicBorder((s) => accent(s)));
|
|
211
|
+
|
|
212
|
+
function executorSummary(): string {
|
|
213
|
+
if (!state.executorId) return dim("Executor: ") + "auto";
|
|
214
|
+
return dim("Executor: ") + (nameById.get(state.executorId) ?? state.executorId);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function configRowText(i: number): string {
|
|
218
|
+
const row = configRows[i];
|
|
219
|
+
const focused = focus === "config" && i === configIndex;
|
|
220
|
+
const cursor = focused ? accent("› ") : " ";
|
|
221
|
+
const label = focused ? accent(row.label) : dim(row.label);
|
|
222
|
+
const value = focused ? theme.bold(row.get()) : row.get();
|
|
223
|
+
return `${cursor}${label}: ${value}`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function currentHint(): string {
|
|
227
|
+
if (focus === "models" && searching) return dim("type to filter • ↑/↓ move • Enter/Esc done");
|
|
228
|
+
if (focus === "config") {
|
|
229
|
+
const note = configRows[configIndex].note();
|
|
230
|
+
return dim(`↑/↓ setting • Space/←→ change • Tab models • Enter save • Esc cancel${note ? " — " + note : ""}`);
|
|
231
|
+
}
|
|
232
|
+
return dim("↑/↓ move • e executor • x auto • / search • Tab config • Enter save • Esc cancel");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function refresh() {
|
|
236
|
+
const prev = selectList.getSelectedItem()?.value;
|
|
237
|
+
const items = makeItems(filterModels(models, query));
|
|
238
|
+
setSelectListItems(selectList, items);
|
|
239
|
+
const idx = prev ? items.findIndex((i) => i.value === prev) : 0;
|
|
240
|
+
selectList.setSelectedIndex(idx >= 0 ? idx : 0);
|
|
241
|
+
|
|
242
|
+
executorLine.setText(executorSummary());
|
|
243
|
+
modelsHeader.setText(focus === "models" ? accent("▸ Models") : dim(" Models"));
|
|
244
|
+
configHeader.setText(focus === "config" ? accent("▸ Config") : dim(" Config"));
|
|
245
|
+
searchLine.setText(
|
|
246
|
+
searching
|
|
247
|
+
? dim(" search: ") + query + accent("▏")
|
|
248
|
+
: query
|
|
249
|
+
? dim(` filter: ${query} (/ to edit)`)
|
|
250
|
+
: dim(" / to search"),
|
|
251
|
+
);
|
|
252
|
+
configTexts.forEach((t, i) => t.setText(configRowText(i)));
|
|
253
|
+
hint.setText(currentHint());
|
|
254
|
+
selectList.invalidate();
|
|
255
|
+
tui.requestRender();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function cycleConfig(dir: 1 | -1) {
|
|
259
|
+
const row = configRows[configIndex];
|
|
260
|
+
const i = row.values.indexOf(row.get());
|
|
261
|
+
const base = i < 0 ? 0 : i;
|
|
262
|
+
row.set(row.values[(base + dir + row.values.length) % row.values.length]);
|
|
263
|
+
refresh();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function confirm() {
|
|
267
|
+
done({
|
|
268
|
+
executorId: state.executorId,
|
|
269
|
+
executorAuto: state.executorAuto ?? !state.executorId,
|
|
270
|
+
mode: state.mode,
|
|
271
|
+
executorTools: state.executorTools,
|
|
272
|
+
maxToolCalls: state.maxToolCalls,
|
|
273
|
+
toolsConsented: state.toolsConsented,
|
|
274
|
+
footerDisplay: state.footerDisplay,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
refresh();
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
render(width: number) {
|
|
282
|
+
return container.render(width);
|
|
283
|
+
},
|
|
284
|
+
invalidate() {
|
|
285
|
+
container.invalidate();
|
|
286
|
+
},
|
|
287
|
+
handleInput(data: string) {
|
|
288
|
+
if (focus === "models" && searching) {
|
|
289
|
+
if (matchesKey(data, Key.escape) || matchesKey(data, Key.enter) || matchesKey(data, Key.return)) {
|
|
290
|
+
searching = false;
|
|
291
|
+
refresh();
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (matchesKey(data, Key.up) || matchesKey(data, Key.down)) {
|
|
295
|
+
selectList.handleInput(data);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const before = searchBuffer.getValue();
|
|
299
|
+
searchBuffer.handleInput(data);
|
|
300
|
+
const after = searchBuffer.getValue();
|
|
301
|
+
if (after !== before) {
|
|
302
|
+
query = after;
|
|
303
|
+
refresh();
|
|
304
|
+
}
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (matchesKey(data, Key.escape)) {
|
|
309
|
+
done(null);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (matchesKey(data, Key.tab)) {
|
|
313
|
+
focus = focus === "models" ? "config" : "models";
|
|
314
|
+
refresh();
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
if (matchesKey(data, Key.enter) || matchesKey(data, Key.return)) {
|
|
318
|
+
confirm();
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (focus === "config") {
|
|
323
|
+
if (matchesKey(data, Key.up)) {
|
|
324
|
+
configIndex = (configIndex - 1 + configRows.length) % configRows.length;
|
|
325
|
+
refresh();
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (matchesKey(data, Key.down)) {
|
|
329
|
+
configIndex = (configIndex + 1) % configRows.length;
|
|
330
|
+
refresh();
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (matchesKey(data, Key.space) || matchesKey(data, Key.right)) {
|
|
334
|
+
cycleConfig(1);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (matchesKey(data, Key.left)) {
|
|
338
|
+
cycleConfig(-1);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (matchesKey(data, Key.up) || matchesKey(data, Key.down)) {
|
|
345
|
+
selectList.handleInput(data);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (data === "/") {
|
|
349
|
+
searching = true;
|
|
350
|
+
refresh();
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (data === "e") {
|
|
354
|
+
const item = selectList.getSelectedItem();
|
|
355
|
+
if (item) {
|
|
356
|
+
state.executorId = toggleExecutorSelection(state.executorId, item.value);
|
|
357
|
+
state.executorAuto = !state.executorId;
|
|
358
|
+
refresh();
|
|
359
|
+
}
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (data === "x") {
|
|
363
|
+
state.executorId = undefined;
|
|
364
|
+
state.executorAuto = true;
|
|
365
|
+
refresh();
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
});
|
|
370
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared pure utilities for pi-devin-fusion.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Run `items` through `fn` with at most `limit` concurrent invocations.
|
|
7
|
+
* Preserves input order in the resolved result array.
|
|
8
|
+
*/
|
|
9
|
+
export async function mapWithConcurrencyLimit<T, R>(
|
|
10
|
+
items: T[],
|
|
11
|
+
limit: number,
|
|
12
|
+
fn: (item: T, index: number) => Promise<R>,
|
|
13
|
+
): Promise<R[]> {
|
|
14
|
+
if (limit <= 0) limit = 1;
|
|
15
|
+
const results: R[] = new Array(items.length);
|
|
16
|
+
let cursor = 0;
|
|
17
|
+
async function worker(): Promise<void> {
|
|
18
|
+
while (cursor < items.length) {
|
|
19
|
+
const index = cursor++;
|
|
20
|
+
results[index] = await fn(items[index], index);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());
|
|
24
|
+
await Promise.all(workers);
|
|
25
|
+
return results;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** UTF-8 safe truncation on a char boundary, appending `suffix` when actually cut. */
|
|
29
|
+
export function truncateToBytes(text: string, maxBytes: number, suffix = ""): string {
|
|
30
|
+
const encoder = new TextEncoder();
|
|
31
|
+
const bytes = encoder.encode(text);
|
|
32
|
+
if (bytes.length <= maxBytes) return text;
|
|
33
|
+
let end = maxBytes;
|
|
34
|
+
// Back off to a char boundary.
|
|
35
|
+
while (end > 0 && (bytes[end] & 0xc0) === 0x80) end--;
|
|
36
|
+
const slice = bytes.subarray(0, end);
|
|
37
|
+
return new TextDecoder().decode(slice) + suffix;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Extract a JSON object from text. Tries the whole text first, then fenced
|
|
42
|
+
* ```json blocks, then the first balanced {...} substring.
|
|
43
|
+
*/
|
|
44
|
+
export function extractJson(text: string): unknown {
|
|
45
|
+
const trimmed = text.trim();
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(trimmed);
|
|
48
|
+
} catch {
|
|
49
|
+
// not strict JSON; continue
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const fence = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
53
|
+
if (fence) {
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(fence[1].trim());
|
|
56
|
+
} catch {
|
|
57
|
+
// ignore and try brace scan
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const brace = text.match(/\{[\s\S]*\}/);
|
|
62
|
+
if (brace) {
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(brace[0]);
|
|
65
|
+
} catch {
|
|
66
|
+
// ignore
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
"noEmit": true,
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"types": ["node"]
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*.ts"]
|
|
16
|
+
}
|