engram-mcp-server 1.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/README.md +645 -0
- package/dist/constants.d.ts +21 -0
- package/dist/constants.js +81 -0
- package/dist/database.d.ts +30 -0
- package/dist/database.js +134 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +67 -0
- package/dist/migrations.d.ts +4 -0
- package/dist/migrations.js +342 -0
- package/dist/scripts/install-hooks.d.ts +3 -0
- package/dist/scripts/install-hooks.js +89 -0
- package/dist/tools/intelligence.d.ts +3 -0
- package/dist/tools/intelligence.js +427 -0
- package/dist/tools/maintenance.d.ts +3 -0
- package/dist/tools/maintenance.js +646 -0
- package/dist/tools/memory.d.ts +3 -0
- package/dist/tools/memory.js +446 -0
- package/dist/tools/scheduler.d.ts +3 -0
- package/dist/tools/scheduler.js +363 -0
- package/dist/tools/sessions.d.ts +3 -0
- package/dist/tools/sessions.js +355 -0
- package/dist/tools/tasks.d.ts +3 -0
- package/dist/tools/tasks.js +206 -0
- package/dist/types.d.ts +170 -0
- package/dist/types.js +5 -0
- package/dist/utils.d.ts +58 -0
- package/dist/utils.js +190 -0
- package/docs/scheduled-events.md +150 -0
- package/package.json +43 -0
- package/scripts/install-mcp.js +175 -0
- package/src/constants.ts +86 -0
- package/src/database.ts +162 -0
- package/src/index.ts +79 -0
- package/src/migrations.ts +367 -0
- package/src/scripts/install-hooks.ts +96 -0
- package/src/tools/intelligence.ts +469 -0
- package/src/tools/maintenance.ts +783 -0
- package/src/tools/memory.ts +543 -0
- package/src/tools/scheduler.ts +413 -0
- package/src/tools/sessions.ts +430 -0
- package/src/tools/tasks.ts +215 -0
- package/src/types.ts +267 -0
- package/src/utils.ts +216 -0
- package/tsconfig.json +19 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Engram MCP Server — Type Definitions
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
// ─── Database Row Types ─────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
export interface SessionRow {
|
|
8
|
+
id: number;
|
|
9
|
+
started_at: string;
|
|
10
|
+
ended_at: string | null;
|
|
11
|
+
summary: string | null;
|
|
12
|
+
agent_name: string;
|
|
13
|
+
project_root: string;
|
|
14
|
+
tags: string | null; // JSON array
|
|
15
|
+
parent_session_id: number | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ChangeRow {
|
|
19
|
+
id: number;
|
|
20
|
+
session_id: number | null;
|
|
21
|
+
timestamp: string;
|
|
22
|
+
file_path: string;
|
|
23
|
+
change_type: ChangeType;
|
|
24
|
+
description: string;
|
|
25
|
+
diff_summary: string | null;
|
|
26
|
+
impact_scope: ImpactScope;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface DecisionRow {
|
|
30
|
+
id: number;
|
|
31
|
+
session_id: number | null;
|
|
32
|
+
timestamp: string;
|
|
33
|
+
decision: string;
|
|
34
|
+
rationale: string | null;
|
|
35
|
+
affected_files: string | null; // JSON array
|
|
36
|
+
tags: string | null; // JSON array
|
|
37
|
+
status: DecisionStatus;
|
|
38
|
+
superseded_by: number | null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface FileNoteRow {
|
|
42
|
+
file_path: string;
|
|
43
|
+
purpose: string | null;
|
|
44
|
+
dependencies: string | null; // JSON array
|
|
45
|
+
dependents: string | null; // JSON array
|
|
46
|
+
layer: ArchLayer | null;
|
|
47
|
+
last_reviewed: string | null;
|
|
48
|
+
last_modified_session: number | null;
|
|
49
|
+
notes: string | null;
|
|
50
|
+
complexity: Complexity | null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface ConventionRow {
|
|
54
|
+
id: number;
|
|
55
|
+
session_id: number | null;
|
|
56
|
+
timestamp: string;
|
|
57
|
+
category: ConventionCategory;
|
|
58
|
+
rule: string;
|
|
59
|
+
examples: string | null; // JSON array
|
|
60
|
+
enforced: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface TaskRow {
|
|
64
|
+
id: number;
|
|
65
|
+
session_id: number | null;
|
|
66
|
+
created_at: string;
|
|
67
|
+
updated_at: string;
|
|
68
|
+
title: string;
|
|
69
|
+
description: string | null;
|
|
70
|
+
status: TaskStatus;
|
|
71
|
+
priority: TaskPriority;
|
|
72
|
+
assigned_files: string | null; // JSON array
|
|
73
|
+
tags: string | null; // JSON array
|
|
74
|
+
completed_at: string | null;
|
|
75
|
+
blocked_by: string | null; // JSON array of task IDs
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface SnapshotRow {
|
|
79
|
+
key: string;
|
|
80
|
+
value: string;
|
|
81
|
+
updated_at: string;
|
|
82
|
+
ttl_minutes: number | null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface MilestoneRow {
|
|
86
|
+
id: number;
|
|
87
|
+
session_id: number | null;
|
|
88
|
+
timestamp: string;
|
|
89
|
+
title: string;
|
|
90
|
+
description: string | null;
|
|
91
|
+
version: string | null;
|
|
92
|
+
tags: string | null; // JSON array
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface ScheduledEventRow {
|
|
96
|
+
id: number;
|
|
97
|
+
session_id: number | null;
|
|
98
|
+
created_at: string;
|
|
99
|
+
title: string;
|
|
100
|
+
description: string | null;
|
|
101
|
+
trigger_type: EventTriggerType;
|
|
102
|
+
trigger_value: string | null;
|
|
103
|
+
status: EventStatus;
|
|
104
|
+
triggered_at: string | null;
|
|
105
|
+
acknowledged_at: string | null;
|
|
106
|
+
requires_approval: number; // 0 or 1 (SQLite boolean)
|
|
107
|
+
action_summary: string | null;
|
|
108
|
+
action_data: string | null; // JSON
|
|
109
|
+
priority: TaskPriority;
|
|
110
|
+
tags: string | null; // JSON array
|
|
111
|
+
recurrence: EventRecurrence | null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ─── Enum Types ─────────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
export type ChangeType =
|
|
117
|
+
| "created"
|
|
118
|
+
| "modified"
|
|
119
|
+
| "deleted"
|
|
120
|
+
| "refactored"
|
|
121
|
+
| "renamed"
|
|
122
|
+
| "moved"
|
|
123
|
+
| "config_changed";
|
|
124
|
+
|
|
125
|
+
export type ImpactScope =
|
|
126
|
+
| "local" // Single file change
|
|
127
|
+
| "module" // Affects a module/package
|
|
128
|
+
| "cross_module"// Affects multiple modules
|
|
129
|
+
| "global"; // Architecture-level change
|
|
130
|
+
|
|
131
|
+
export type DecisionStatus =
|
|
132
|
+
| "active"
|
|
133
|
+
| "superseded"
|
|
134
|
+
| "deprecated"
|
|
135
|
+
| "experimental";
|
|
136
|
+
|
|
137
|
+
export type ArchLayer =
|
|
138
|
+
| "ui"
|
|
139
|
+
| "viewmodel"
|
|
140
|
+
| "domain"
|
|
141
|
+
| "data"
|
|
142
|
+
| "network"
|
|
143
|
+
| "database"
|
|
144
|
+
| "di"
|
|
145
|
+
| "util"
|
|
146
|
+
| "test"
|
|
147
|
+
| "config"
|
|
148
|
+
| "build"
|
|
149
|
+
| "other";
|
|
150
|
+
|
|
151
|
+
export type Complexity =
|
|
152
|
+
| "trivial"
|
|
153
|
+
| "simple"
|
|
154
|
+
| "moderate"
|
|
155
|
+
| "complex"
|
|
156
|
+
| "critical";
|
|
157
|
+
|
|
158
|
+
export type ConventionCategory =
|
|
159
|
+
| "naming"
|
|
160
|
+
| "architecture"
|
|
161
|
+
| "styling"
|
|
162
|
+
| "testing"
|
|
163
|
+
| "git"
|
|
164
|
+
| "documentation"
|
|
165
|
+
| "error_handling"
|
|
166
|
+
| "performance"
|
|
167
|
+
| "security"
|
|
168
|
+
| "other";
|
|
169
|
+
|
|
170
|
+
export type TaskStatus =
|
|
171
|
+
| "backlog"
|
|
172
|
+
| "in_progress"
|
|
173
|
+
| "blocked"
|
|
174
|
+
| "review"
|
|
175
|
+
| "done"
|
|
176
|
+
| "cancelled";
|
|
177
|
+
|
|
178
|
+
export type TaskPriority =
|
|
179
|
+
| "critical"
|
|
180
|
+
| "high"
|
|
181
|
+
| "medium"
|
|
182
|
+
| "low";
|
|
183
|
+
|
|
184
|
+
export type EventTriggerType =
|
|
185
|
+
| "next_session"
|
|
186
|
+
| "datetime"
|
|
187
|
+
| "task_complete"
|
|
188
|
+
| "manual";
|
|
189
|
+
|
|
190
|
+
export type EventStatus =
|
|
191
|
+
| "pending"
|
|
192
|
+
| "triggered"
|
|
193
|
+
| "acknowledged"
|
|
194
|
+
| "executed"
|
|
195
|
+
| "cancelled"
|
|
196
|
+
| "snoozed";
|
|
197
|
+
|
|
198
|
+
export type EventRecurrence =
|
|
199
|
+
| "once"
|
|
200
|
+
| "every_session"
|
|
201
|
+
| "daily"
|
|
202
|
+
| "weekly";
|
|
203
|
+
|
|
204
|
+
// ─── Response Types ─────────────────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
export interface SessionContext {
|
|
207
|
+
session_id: number;
|
|
208
|
+
previous_session: {
|
|
209
|
+
id: number;
|
|
210
|
+
summary: string | null;
|
|
211
|
+
ended_at: string | null;
|
|
212
|
+
agent: string;
|
|
213
|
+
} | null;
|
|
214
|
+
changes_since_last: {
|
|
215
|
+
recorded: ChangeRow[];
|
|
216
|
+
git_log: string;
|
|
217
|
+
};
|
|
218
|
+
active_decisions: DecisionRow[];
|
|
219
|
+
active_conventions: ConventionRow[];
|
|
220
|
+
open_tasks: TaskRow[];
|
|
221
|
+
project_snapshot_age_minutes: number | null;
|
|
222
|
+
message: string;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export interface ProjectSnapshot {
|
|
226
|
+
project_root: string;
|
|
227
|
+
file_tree: string[];
|
|
228
|
+
total_files: number;
|
|
229
|
+
file_notes: FileNoteRow[];
|
|
230
|
+
recent_decisions: DecisionRow[];
|
|
231
|
+
active_conventions: ConventionRow[];
|
|
232
|
+
layer_distribution: Record<string, number>;
|
|
233
|
+
generated_at: string;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export interface MemoryStats {
|
|
237
|
+
total_sessions: number;
|
|
238
|
+
total_changes: number;
|
|
239
|
+
total_decisions: number;
|
|
240
|
+
total_file_notes: number;
|
|
241
|
+
total_conventions: number;
|
|
242
|
+
total_tasks: number;
|
|
243
|
+
total_milestones: number;
|
|
244
|
+
oldest_session: string | null;
|
|
245
|
+
newest_session: string | null;
|
|
246
|
+
most_changed_files: Array<{ file_path: string; change_count: number }>;
|
|
247
|
+
database_size_kb: number;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export interface CompactionResult {
|
|
251
|
+
sessions_compacted: number;
|
|
252
|
+
changes_summarized: number;
|
|
253
|
+
storage_freed_kb: number;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export interface ConfigRow {
|
|
257
|
+
key: string;
|
|
258
|
+
value: string;
|
|
259
|
+
updated_at: string;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export interface BackupInfo {
|
|
263
|
+
path: string;
|
|
264
|
+
size_kb: number;
|
|
265
|
+
created_at: string;
|
|
266
|
+
database_version: number;
|
|
267
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Engram MCP Server — Utilities
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import { execSync } from "child_process";
|
|
6
|
+
import * as fs from "fs";
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
import {
|
|
9
|
+
EXCLUDED_DIRS,
|
|
10
|
+
LAYER_PATTERNS,
|
|
11
|
+
MAX_FILE_TREE_DEPTH,
|
|
12
|
+
MAX_FILE_TREE_ENTRIES,
|
|
13
|
+
PROJECT_MARKERS,
|
|
14
|
+
} from "./constants.js";
|
|
15
|
+
import type { ArchLayer } from "./types.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Auto-detect the project root by walking up from cwd looking for marker files.
|
|
19
|
+
*/
|
|
20
|
+
export function findProjectRoot(startDir?: string): string {
|
|
21
|
+
// 1. Explicit env var
|
|
22
|
+
if (process.env.ENGRAM_PROJECT_ROOT) return process.env.ENGRAM_PROJECT_ROOT;
|
|
23
|
+
if (process.env.PROJECT_ROOT) return process.env.PROJECT_ROOT;
|
|
24
|
+
|
|
25
|
+
// 2. Walk up directory tree
|
|
26
|
+
let dir = startDir || process.cwd();
|
|
27
|
+
while (dir !== path.dirname(dir)) {
|
|
28
|
+
for (const marker of PROJECT_MARKERS) {
|
|
29
|
+
if (fs.existsSync(path.join(dir, marker))) {
|
|
30
|
+
return dir;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
dir = path.dirname(dir);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 3. Fallback to cwd
|
|
37
|
+
return process.cwd();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Scan the project file tree, respecting exclusions and depth limits.
|
|
42
|
+
*/
|
|
43
|
+
export function scanFileTree(
|
|
44
|
+
rootDir: string,
|
|
45
|
+
maxDepth: number = MAX_FILE_TREE_DEPTH,
|
|
46
|
+
maxEntries: number = MAX_FILE_TREE_ENTRIES
|
|
47
|
+
): string[] {
|
|
48
|
+
const files: string[] = [];
|
|
49
|
+
|
|
50
|
+
// Merge .engramignore entries with the default exclusion set
|
|
51
|
+
const exclusions = new Set(EXCLUDED_DIRS);
|
|
52
|
+
try {
|
|
53
|
+
const ignorePath = path.join(rootDir, ".engramignore");
|
|
54
|
+
if (fs.existsSync(ignorePath)) {
|
|
55
|
+
const lines = fs.readFileSync(ignorePath, "utf-8").split("\n");
|
|
56
|
+
for (const line of lines) {
|
|
57
|
+
const trimmed = line.trim();
|
|
58
|
+
if (trimmed && !trimmed.startsWith("#")) {
|
|
59
|
+
exclusions.add(trimmed.replace(/\/$/, "")); // Strip trailing slash
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch { /* .engramignore is optional */ }
|
|
64
|
+
|
|
65
|
+
function walk(dir: string, depth: number): void {
|
|
66
|
+
if (depth > maxDepth || files.length >= maxEntries) return;
|
|
67
|
+
|
|
68
|
+
let entries: fs.Dirent[];
|
|
69
|
+
try {
|
|
70
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
71
|
+
} catch {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Sort for deterministic output
|
|
76
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
77
|
+
|
|
78
|
+
for (const entry of entries) {
|
|
79
|
+
if (files.length >= maxEntries) break;
|
|
80
|
+
if (entry.name.startsWith(".") && entry.isDirectory()) continue;
|
|
81
|
+
|
|
82
|
+
if (entry.isDirectory()) {
|
|
83
|
+
if (exclusions.has(entry.name)) continue;
|
|
84
|
+
const subPath = path.relative(rootDir, path.join(dir, entry.name));
|
|
85
|
+
files.push(subPath + "/");
|
|
86
|
+
walk(path.join(dir, entry.name), depth + 1);
|
|
87
|
+
} else {
|
|
88
|
+
const relPath = path.relative(rootDir, path.join(dir, entry.name));
|
|
89
|
+
files.push(relPath);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
walk(rootDir, 0);
|
|
95
|
+
return files;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Detect the architectural layer of a file based on its path.
|
|
100
|
+
*/
|
|
101
|
+
export function detectLayer(filePath: string): ArchLayer {
|
|
102
|
+
const normalized = "/" + filePath.replace(/\\/g, "/");
|
|
103
|
+
for (const [layer, patterns] of Object.entries(LAYER_PATTERNS)) {
|
|
104
|
+
for (const pattern of patterns) {
|
|
105
|
+
if (pattern.test(normalized)) {
|
|
106
|
+
return layer as ArchLayer;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return "other";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Run a git command in the project root. Returns empty string on failure.
|
|
115
|
+
*/
|
|
116
|
+
export function gitCommand(projectRoot: string, command: string): string {
|
|
117
|
+
try {
|
|
118
|
+
return execSync(`cd "${projectRoot}" && git ${command}`, {
|
|
119
|
+
encoding: "utf-8",
|
|
120
|
+
timeout: 10000,
|
|
121
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
122
|
+
}).trim();
|
|
123
|
+
} catch {
|
|
124
|
+
return "";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Check if the project is a git repository.
|
|
130
|
+
*/
|
|
131
|
+
export function isGitRepo(projectRoot: string): boolean {
|
|
132
|
+
return fs.existsSync(path.join(projectRoot, ".git"));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get git log since a given timestamp.
|
|
137
|
+
*/
|
|
138
|
+
export function getGitLogSince(projectRoot: string, since: string, limit: number = 50): string {
|
|
139
|
+
return gitCommand(
|
|
140
|
+
projectRoot,
|
|
141
|
+
`log --name-status --since="${since}" --pretty=format:"[%h] %s (%ar)" -${limit}`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get git diff stat (files changed, insertions, deletions).
|
|
147
|
+
*/
|
|
148
|
+
export function getGitDiffStat(projectRoot: string, since: string): string {
|
|
149
|
+
return gitCommand(projectRoot, `diff --stat HEAD@{${since}} 2>/dev/null`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get files changed in git since a timestamp.
|
|
154
|
+
*/
|
|
155
|
+
export function getGitFilesChanged(projectRoot: string, since: string): string[] {
|
|
156
|
+
const result = gitCommand(
|
|
157
|
+
projectRoot,
|
|
158
|
+
`log --name-only --since="${since}" --pretty=format:"" 2>/dev/null`
|
|
159
|
+
);
|
|
160
|
+
if (!result) return [];
|
|
161
|
+
return [...new Set(result.split("\n").filter(Boolean))];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get the current git branch name.
|
|
166
|
+
*/
|
|
167
|
+
export function getGitBranch(projectRoot: string): string {
|
|
168
|
+
return gitCommand(projectRoot, "rev-parse --abbrev-ref HEAD");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get the latest git commit hash (short).
|
|
173
|
+
*/
|
|
174
|
+
export function getGitHead(projectRoot: string): string {
|
|
175
|
+
return gitCommand(projectRoot, "rev-parse --short HEAD");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Safely parse a JSON string, returning a default value on failure.
|
|
180
|
+
*/
|
|
181
|
+
export function safeJsonParse<T>(json: string | null | undefined, fallback: T): T {
|
|
182
|
+
if (!json) return fallback;
|
|
183
|
+
try {
|
|
184
|
+
return JSON.parse(json) as T;
|
|
185
|
+
} catch {
|
|
186
|
+
return fallback;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Format a response as either JSON or Markdown.
|
|
192
|
+
*/
|
|
193
|
+
export function formatResponse(data: unknown, format: "json" | "markdown" = "json"): string {
|
|
194
|
+
if (format === "json") {
|
|
195
|
+
return JSON.stringify(data, null, 2);
|
|
196
|
+
}
|
|
197
|
+
// For markdown, we just pretty-print JSON in a code block
|
|
198
|
+
return "```json\n" + JSON.stringify(data, null, 2) + "\n```";
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Calculate minutes between now and a given ISO timestamp.
|
|
203
|
+
*/
|
|
204
|
+
export function minutesSince(isoTimestamp: string): number {
|
|
205
|
+
const then = new Date(isoTimestamp).getTime();
|
|
206
|
+
const diff = Date.now() - then;
|
|
207
|
+
return Math.floor(diff / 60000);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Truncate a string to a max length with ellipsis.
|
|
212
|
+
*/
|
|
213
|
+
export function truncate(str: string, maxLength: number): string {
|
|
214
|
+
if (str.length <= maxLength) return str;
|
|
215
|
+
return str.slice(0, maxLength - 3) + "...";
|
|
216
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "Node16",
|
|
5
|
+
"moduleResolution": "Node16",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["src/**/*"],
|
|
18
|
+
"exclude": ["node_modules", "dist"]
|
|
19
|
+
}
|