@steerable/agent-shell 0.6.30 → 0.6.31
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/host/runtime.js +7 -1
- package/dist/local-backend/router.d.ts +1 -0
- package/dist/local-backend/router.js +32 -8
- package/dist/project-home.d.ts +23 -0
- package/dist/project-home.js +47 -0
- package/dist/project-registry.d.ts +12 -5
- package/dist/project-registry.js +24 -4
- package/dist/runtime.d.ts +6 -0
- package/dist/runtime.js +19 -0
- package/dist/sidecar/boot.d.ts +1 -1
- package/dist/sidecar/reverse-tools.d.ts +1 -1
- package/dist/sidecar/reverse-tools.js +1 -1
- package/package.json +4 -4
package/dist/host/runtime.js
CHANGED
|
@@ -207,7 +207,13 @@ export async function createHostRuntime(options) {
|
|
|
207
207
|
resolveProjectRoot: async (chatId) => (await localBackendRouter.resolveChatProject(chatId))?.folderPath ?? null,
|
|
208
208
|
// 项目模式下文件读写被围栏在项目目录内;会话附件目录是额外放行的
|
|
209
209
|
// 只读根,保证用户上传的文件即使在项目会话里也能被 agent 读回。
|
|
210
|
-
resolveAdditionalReadRoots: (chatId) =>
|
|
210
|
+
resolveAdditionalReadRoots: async (chatId) => {
|
|
211
|
+
const project = await localBackendRouter.resolveChatProject(chatId);
|
|
212
|
+
return [
|
|
213
|
+
getChatAttachmentsDir(chatId),
|
|
214
|
+
...(project?.sourceFolders ?? []),
|
|
215
|
+
];
|
|
216
|
+
},
|
|
211
217
|
approvalHandler: approvalBridge.handler,
|
|
212
218
|
askUserHandler: askUserBridge.handler,
|
|
213
219
|
// P2b: resume 时 sidecar 把记录里的读证据推给 LocalExecutor 的
|
|
@@ -38,12 +38,18 @@ import { detectInterruptedTurn } from './interrupted-helper.js';
|
|
|
38
38
|
import { dropCurrentUserMessage } from './history-helper.js';
|
|
39
39
|
import { parseImageAttachments, processImageAttachments } from '../image-attachment.js';
|
|
40
40
|
import { loadProjectRuleFiles } from '../project-rules.js';
|
|
41
|
+
import { allocateProjectHome, ensureProjectHome } from '../project-home.js';
|
|
41
42
|
import { registerLiveStream, getLiveStream, removeLiveStream } from './live-stream.js';
|
|
42
43
|
import { beginPackTurnObservers, collectPackExecWritableRoots, collectPackForcedSkillVars, collectPackWorldState, } from './pack-turn-hooks.js';
|
|
43
44
|
import { matchPackBackendRoute } from './pack-backend-routes.js';
|
|
44
45
|
import { collectTurnFiles } from './turn-files.js';
|
|
45
46
|
import { resolveMentionedPaths } from './mentioned-paths.js';
|
|
46
47
|
import { getAuthProvider } from '../auth/index.js';
|
|
48
|
+
function parseSourceFolders(value) {
|
|
49
|
+
if (!Array.isArray(value))
|
|
50
|
+
return [];
|
|
51
|
+
return value.filter((item) => typeof item === 'string' && item.trim() !== '');
|
|
52
|
+
}
|
|
47
53
|
/**
|
|
48
54
|
* 把 CoreLoop/LLM 的底层错误翻译成适合直接显示在助手消息里的中文提示。
|
|
49
55
|
* 原始错误仍保存在 messageMetadata.completionReason 里,便于排查。
|
|
@@ -431,9 +437,17 @@ export class LocalBackendRouter {
|
|
|
431
437
|
if (method === 'POST') {
|
|
432
438
|
const payload = this.toRecord(request.body);
|
|
433
439
|
try {
|
|
440
|
+
const name = String(payload.name || '');
|
|
441
|
+
const sourceFolders = parseSourceFolders(payload.sourceFolders);
|
|
442
|
+
let folderPath = typeof payload.folderPath === 'string' ? payload.folderPath.trim() : '';
|
|
443
|
+
if (!folderPath) {
|
|
444
|
+
folderPath = allocateProjectHome(name);
|
|
445
|
+
ensureProjectHome(folderPath);
|
|
446
|
+
}
|
|
434
447
|
const project = registry.create({
|
|
435
|
-
name
|
|
436
|
-
folderPath
|
|
448
|
+
name,
|
|
449
|
+
folderPath,
|
|
450
|
+
sourceFolders,
|
|
437
451
|
});
|
|
438
452
|
return { status: 200, data: { success: true, project } };
|
|
439
453
|
}
|
|
@@ -458,6 +472,9 @@ export class LocalBackendRouter {
|
|
|
458
472
|
const project = registry.update(projectId, {
|
|
459
473
|
name: typeof payload.name === 'string' ? payload.name : undefined,
|
|
460
474
|
folderPath: typeof payload.folderPath === 'string' ? payload.folderPath : undefined,
|
|
475
|
+
sourceFolders: payload.sourceFolders === undefined
|
|
476
|
+
? undefined
|
|
477
|
+
: parseSourceFolders(payload.sourceFolders),
|
|
461
478
|
});
|
|
462
479
|
return { status: 200, data: { success: true, project } };
|
|
463
480
|
}
|
|
@@ -2185,7 +2202,11 @@ export class LocalBackendRouter {
|
|
|
2185
2202
|
const project = this.toolRouter.projectRegistry?.get(projectId);
|
|
2186
2203
|
if (!project)
|
|
2187
2204
|
return null;
|
|
2188
|
-
return {
|
|
2205
|
+
return {
|
|
2206
|
+
name: project.name,
|
|
2207
|
+
folderPath: project.folderPath,
|
|
2208
|
+
sourceFolders: project.sourceFolders ?? [],
|
|
2209
|
+
};
|
|
2189
2210
|
}
|
|
2190
2211
|
/**
|
|
2191
2212
|
* 解析本轮生效的智能体:人设前言、技能勾选、工具策略同源于这一次解析。
|
|
@@ -2377,11 +2398,14 @@ export class LocalBackendRouter {
|
|
|
2377
2398
|
const chatProject = await this.resolveChatProject(chatId);
|
|
2378
2399
|
if (chatProject) {
|
|
2379
2400
|
systemPrompt +=
|
|
2380
|
-
`\n\n【项目模式】当前对话绑定项目「${chatProject.name}
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2401
|
+
`\n\n【项目模式】当前对话绑定项目「${chatProject.name}」,家目录:${chatProject.folderPath}\n` +
|
|
2402
|
+
`你的文件写入(local_write_file)和命令执行(local_exec_shell)都被限制在该家目录内:` +
|
|
2403
|
+
`写入路径越界会被拒绝;命令默认在家目录下运行,显式指定的 cwd 越界也会被拒绝。` +
|
|
2404
|
+
(chatProject.sourceFolders.length > 0
|
|
2405
|
+
? `另有源文件夹(只读):${chatProject.sourceFolders.join('、')}。`
|
|
2406
|
+
: '') +
|
|
2407
|
+
`请一律使用项目目录内的路径(相对路径按家目录解析)。` +
|
|
2408
|
+
`如确需访问项目外的文件,向用户说明该限制,或请其在项目里附加为源文件夹。`;
|
|
2385
2409
|
// W6-5 + W6-7a:项目级规则文件(AGENTS.md / CLAUDE.md)是不可信输入,
|
|
2386
2410
|
// 仅在用户显式信任该项目后才注入模型上下文——未信任一律不读取、不注入
|
|
2387
2411
|
// (打开恶意仓库时,一段构造的规则文件不能劫持 agent)。信任状态持久化、
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 项目默认家目录:`Documents/<应用名>/<项目名>/`。
|
|
3
|
+
*
|
|
4
|
+
* 新建项目不再等于「选一个已有文件夹」——项目是带名字的容器,
|
|
5
|
+
* 家目录由本模块分配并创建。用户另加的源文件夹是附加只读根,
|
|
6
|
+
* 不替代这个家目录。
|
|
7
|
+
*/
|
|
8
|
+
export declare function sanitizeProjectDirName(name: string): string;
|
|
9
|
+
/** `Documents/<应用显示名>`:该应用下所有托管项目的父目录。 */
|
|
10
|
+
export declare function appProjectsRoot(options?: {
|
|
11
|
+
documentsDir?: string;
|
|
12
|
+
appFolderName?: string;
|
|
13
|
+
}): string;
|
|
14
|
+
/**
|
|
15
|
+
* 为项目名分配尚未占用的家目录路径(不落盘)。
|
|
16
|
+
* 已存在同名目录时追加 `-2`、`-3`…
|
|
17
|
+
*/
|
|
18
|
+
export declare function allocateProjectHome(projectName: string, options?: {
|
|
19
|
+
documentsDir?: string;
|
|
20
|
+
appFolderName?: string;
|
|
21
|
+
exists?: (folderPath: string) => boolean;
|
|
22
|
+
}): string;
|
|
23
|
+
export declare function ensureProjectHome(folderPath: string): void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 项目默认家目录:`Documents/<应用名>/<项目名>/`。
|
|
3
|
+
*
|
|
4
|
+
* 新建项目不再等于「选一个已有文件夹」——项目是带名字的容器,
|
|
5
|
+
* 家目录由本模块分配并创建。用户另加的源文件夹是附加只读根,
|
|
6
|
+
* 不替代这个家目录。
|
|
7
|
+
*/
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { getBrand } from './brand.js';
|
|
11
|
+
import { getDocumentsDir } from './runtime.js';
|
|
12
|
+
const UNSAFE_DIR_CHARS = /[\\/:*?"<>|]/g;
|
|
13
|
+
export function sanitizeProjectDirName(name) {
|
|
14
|
+
const trimmed = name
|
|
15
|
+
.trim()
|
|
16
|
+
.replace(UNSAFE_DIR_CHARS, '-')
|
|
17
|
+
.replace(/\s+/g, ' ')
|
|
18
|
+
.replace(/\.+$/g, '');
|
|
19
|
+
return trimmed || '未命名项目';
|
|
20
|
+
}
|
|
21
|
+
/** `Documents/<应用显示名>`:该应用下所有托管项目的父目录。 */
|
|
22
|
+
export function appProjectsRoot(options) {
|
|
23
|
+
const documents = options?.documentsDir ?? getDocumentsDir();
|
|
24
|
+
const appName = options?.appFolderName ?? getBrand().displayName;
|
|
25
|
+
return path.join(documents, sanitizeProjectDirName(appName));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 为项目名分配尚未占用的家目录路径(不落盘)。
|
|
29
|
+
* 已存在同名目录时追加 `-2`、`-3`…
|
|
30
|
+
*/
|
|
31
|
+
export function allocateProjectHome(projectName, options) {
|
|
32
|
+
const root = appProjectsRoot(options);
|
|
33
|
+
const base = sanitizeProjectDirName(projectName);
|
|
34
|
+
const exists = options?.exists ?? ((folderPath) => fs.existsSync(folderPath));
|
|
35
|
+
let candidate = path.join(root, base);
|
|
36
|
+
if (!exists(candidate))
|
|
37
|
+
return candidate;
|
|
38
|
+
for (let i = 2; i < 1000; i += 1) {
|
|
39
|
+
candidate = path.join(root, `${base}-${i}`);
|
|
40
|
+
if (!exists(candidate))
|
|
41
|
+
return candidate;
|
|
42
|
+
}
|
|
43
|
+
throw new Error('无法分配项目目录:重名过多');
|
|
44
|
+
}
|
|
45
|
+
export function ensureProjectHome(folderPath) {
|
|
46
|
+
fs.mkdirSync(folderPath, { recursive: true });
|
|
47
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 项目注册表(项目模式)。
|
|
3
3
|
*
|
|
4
|
-
* 项目 = 名字 +
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* 项目 = 名字 + 托管家目录 + 可选源文件夹。家目录默认建在
|
|
5
|
+
* `Documents/<应用名>/<项目名>/`(见 project-home.ts)。chat 通过
|
|
6
|
+
* `chat_sessions.project_id` 绑定项目;绑定后文件读写与命令执行被硬限制
|
|
7
|
+
* 在家目录内(见 tool-router.ts 的 ToolExecContext.projectRoot);源文件夹
|
|
8
|
+
* 只放宽 local_read_file。
|
|
8
9
|
*
|
|
9
10
|
* 持久化在 userData/agent-projects.json。存储通过 {@link ProjectKvStore}
|
|
10
11
|
* 接口注入:main.ts 用 electron-store 实现,单测用内存实现——本模块不
|
|
@@ -14,8 +15,13 @@ export interface ProjectRecord {
|
|
|
14
15
|
id: string;
|
|
15
16
|
/** 用户可见名称(侧边栏分组标题)。 */
|
|
16
17
|
name: string;
|
|
17
|
-
/**
|
|
18
|
+
/** 托管家目录(绝对路径)。默认 `Documents/<应用名>/<项目名>/`。 */
|
|
18
19
|
folderPath: string;
|
|
20
|
+
/**
|
|
21
|
+
* 附加源文件夹(已有代码目录)。只放宽读取,不替代家目录,也不放宽写入。
|
|
22
|
+
* 旧记录没有此字段,读取时按空列表处理。
|
|
23
|
+
*/
|
|
24
|
+
sourceFolders?: string[];
|
|
19
25
|
/**
|
|
20
26
|
* W6-5 项目信任门控:项目目录里的 `AGENTS.md` / `CLAUDE.md` 等规则文件
|
|
21
27
|
* 是「项目作者写给 agent 的指令」——打开一个恶意仓库时,一段精心构造的
|
|
@@ -30,6 +36,7 @@ export interface ProjectRecord {
|
|
|
30
36
|
export interface CreateProjectInput {
|
|
31
37
|
name: string;
|
|
32
38
|
folderPath: string;
|
|
39
|
+
sourceFolders?: string[];
|
|
33
40
|
}
|
|
34
41
|
/** 最小 KV 存储接口,避免本模块直接依赖 electron-store。 */
|
|
35
42
|
export interface ProjectKvStore {
|
package/dist/project-registry.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 项目注册表(项目模式)。
|
|
3
3
|
*
|
|
4
|
-
* 项目 = 名字 +
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* 项目 = 名字 + 托管家目录 + 可选源文件夹。家目录默认建在
|
|
5
|
+
* `Documents/<应用名>/<项目名>/`(见 project-home.ts)。chat 通过
|
|
6
|
+
* `chat_sessions.project_id` 绑定项目;绑定后文件读写与命令执行被硬限制
|
|
7
|
+
* 在家目录内(见 tool-router.ts 的 ToolExecContext.projectRoot);源文件夹
|
|
8
|
+
* 只放宽 local_read_file。
|
|
8
9
|
*
|
|
9
10
|
* 持久化在 userData/agent-projects.json。存储通过 {@link ProjectKvStore}
|
|
10
11
|
* 接口注入:main.ts 用 electron-store 实现,单测用内存实现——本模块不
|
|
@@ -34,11 +35,13 @@ export class ProjectRegistry {
|
|
|
34
35
|
throw new Error('项目文件夹不能为空');
|
|
35
36
|
if (this.get(name))
|
|
36
37
|
throw new Error(`已存在同名项目「${name}」`);
|
|
38
|
+
const sourceFolders = normalizeSourceFolders(input.sourceFolders, folderPath);
|
|
37
39
|
const now = new Date().toISOString();
|
|
38
40
|
const entry = {
|
|
39
41
|
id: randomUUID(),
|
|
40
42
|
name,
|
|
41
43
|
folderPath,
|
|
44
|
+
...(sourceFolders.length > 0 ? { sourceFolders } : {}),
|
|
42
45
|
createdAt: now,
|
|
43
46
|
updatedAt: now,
|
|
44
47
|
};
|
|
@@ -62,10 +65,14 @@ export class ProjectRegistry {
|
|
|
62
65
|
: current.folderPath;
|
|
63
66
|
if (!nextFolder)
|
|
64
67
|
throw new Error('项目文件夹不能为空');
|
|
68
|
+
const sourceFolders = updates.sourceFolders !== undefined
|
|
69
|
+
? normalizeSourceFolders(updates.sourceFolders, nextFolder)
|
|
70
|
+
: normalizeSourceFolders(current.sourceFolders, nextFolder);
|
|
65
71
|
const next = {
|
|
66
72
|
...current,
|
|
67
73
|
name: nextName,
|
|
68
74
|
folderPath: nextFolder,
|
|
75
|
+
sourceFolders: sourceFolders.length > 0 ? sourceFolders : undefined,
|
|
69
76
|
updatedAt: new Date().toISOString(),
|
|
70
77
|
};
|
|
71
78
|
projects[idx] = next;
|
|
@@ -104,3 +111,16 @@ export class ProjectRegistry {
|
|
|
104
111
|
return next;
|
|
105
112
|
}
|
|
106
113
|
}
|
|
114
|
+
function normalizeSourceFolders(folders, homePath) {
|
|
115
|
+
const home = homePath.trim();
|
|
116
|
+
const seen = new Set();
|
|
117
|
+
const out = [];
|
|
118
|
+
for (const raw of folders ?? []) {
|
|
119
|
+
const folder = raw.trim();
|
|
120
|
+
if (!folder || folder === home || seen.has(folder))
|
|
121
|
+
continue;
|
|
122
|
+
seen.add(folder);
|
|
123
|
+
out.push(folder);
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
export declare function isElectronRuntime(): boolean;
|
|
3
3
|
/** 应用数据目录(SQLite、JSON store、用户技能目录的根)。 */
|
|
4
4
|
export declare function getUserDataDir(): string;
|
|
5
|
+
/**
|
|
6
|
+
* 用户文档目录。项目默认家目录建在这里的「应用名」文件夹下
|
|
7
|
+
* (见 project-home.ts)。测试可用 STEERABLE_DOCUMENTS_DIR 改锚点,
|
|
8
|
+
* 避免写进真实 Documents。
|
|
9
|
+
*/
|
|
10
|
+
export declare function getDocumentsDir(): string;
|
|
5
11
|
/**
|
|
6
12
|
* 注入应用根目录(产品组装根调用)。重复注入不同值抛错(组装期笔误,
|
|
7
13
|
* fail fast——与 setProductBrand 同语义)。
|
package/dist/runtime.js
CHANGED
|
@@ -54,6 +54,25 @@ export function getUserDataDir() {
|
|
|
54
54
|
const dirName = getProductConfig().dataDirName ?? '.agent-shell';
|
|
55
55
|
return path.join(os.homedir(), dirName);
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* 用户文档目录。项目默认家目录建在这里的「应用名」文件夹下
|
|
59
|
+
* (见 project-home.ts)。测试可用 STEERABLE_DOCUMENTS_DIR 改锚点,
|
|
60
|
+
* 避免写进真实 Documents。
|
|
61
|
+
*/
|
|
62
|
+
export function getDocumentsDir() {
|
|
63
|
+
if (process.env.STEERABLE_DOCUMENTS_DIR)
|
|
64
|
+
return process.env.STEERABLE_DOCUMENTS_DIR;
|
|
65
|
+
const app = tryElectronApp();
|
|
66
|
+
if (app) {
|
|
67
|
+
try {
|
|
68
|
+
return app.getPath('documents');
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// 个别环境 getPath('documents') 不可用,回落到 ~/Documents。
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return path.join(os.homedir(), 'Documents');
|
|
75
|
+
}
|
|
57
76
|
/**
|
|
58
77
|
* 应用根目录(消费产品的仓库/打包根:含 products/manifest.json、assets、
|
|
59
78
|
* scripts 的那层)。3.2 起 shell 是被消费的框架包,本模块自己的位置
|
package/dist/sidecar/boot.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export interface HostSidecarDeps {
|
|
|
11
11
|
* 项目模式之外额外放行的只读根(会话附件目录等)。文件写入仍只受
|
|
12
12
|
* projectRoot 围栏约束;这些根只放宽 local_read_file 的读取范围。
|
|
13
13
|
*/
|
|
14
|
-
resolveAdditionalReadRoots?: (chatId: string) => string[]
|
|
14
|
+
resolveAdditionalReadRoots?: (chatId: string) => string[] | Promise<string[]>;
|
|
15
15
|
/** W4-1 审批反向通道处理器(宿主审批弹窗的应答入口)。 */
|
|
16
16
|
approvalHandler: ReturnType<typeof createApprovalBridge>['handler'];
|
|
17
17
|
/**
|
|
@@ -24,6 +24,6 @@ export interface ReverseToolDeps {
|
|
|
24
24
|
* 额外放行的只读根(会话附件目录等)。写入仍只受 projectRoot 围栏约束,
|
|
25
25
|
* 这里只放宽 local_read_file 的读取范围。
|
|
26
26
|
*/
|
|
27
|
-
resolveAdditionalReadRoots?: (chatId: string) => string[]
|
|
27
|
+
resolveAdditionalReadRoots?: (chatId: string) => string[] | Promise<string[]>;
|
|
28
28
|
}
|
|
29
29
|
export declare function createToolInvokeHandler(deps: ReverseToolDeps): SidecarReverseHandler;
|
|
@@ -56,7 +56,7 @@ export function createToolInvokeHandler(deps) {
|
|
|
56
56
|
? (await deps.resolveProjectRoot?.(p.context.chatId) ?? null)
|
|
57
57
|
: null;
|
|
58
58
|
const additionalReadRoots = p.context?.chatId
|
|
59
|
-
? (deps.resolveAdditionalReadRoots?.(p.context.chatId) ?? [])
|
|
59
|
+
? await Promise.resolve(deps.resolveAdditionalReadRoots?.(p.context.chatId) ?? [])
|
|
60
60
|
: [];
|
|
61
61
|
if (name === 'local_exec_shell') {
|
|
62
62
|
const classification = classifyShellCommand(String(toolArgs.command ?? ''));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steerable/agent-shell",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.31",
|
|
4
4
|
"description": "Steerable framework — product-neutral desktop/headless agent host (Tier 5). Electron main + preload + headless HTTP server (BS) sharing one storage/sidecar/tooling core; scenario packs extend it through the @steerable/pack-sdk contract and are composed at build time by the consuming product.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://steerableframework.com/",
|
|
@@ -49,9 +49,9 @@
|
|
|
49
49
|
"electron-store": "^8.1.0",
|
|
50
50
|
"he": "^1.2.0",
|
|
51
51
|
"node-pty": "^1.1.0",
|
|
52
|
-
"@steerable/agent-harness": "0.6.
|
|
53
|
-
"@steerable/pack-sdk": "0.6.
|
|
54
|
-
"@steerable/agent-protocol": "0.6.
|
|
52
|
+
"@steerable/agent-harness": "0.6.31",
|
|
53
|
+
"@steerable/pack-sdk": "0.6.31",
|
|
54
|
+
"@steerable/agent-protocol": "0.6.31"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@types/better-sqlite3": "^7.6.13",
|