@teammates/recall 0.1.1 → 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/README.md +7 -5
- package/dist/cli.js +123 -12
- package/dist/cli.test.d.ts +1 -0
- package/dist/cli.test.js +270 -0
- package/dist/index.d.ts +1 -1
- package/dist/indexer.d.ts +1 -1
- package/dist/indexer.js +45 -9
- package/dist/indexer.test.d.ts +1 -0
- package/dist/indexer.test.js +208 -0
- package/dist/search.d.ts +11 -1
- package/dist/search.js +75 -6
- package/dist/search.test.d.ts +1 -0
- package/dist/search.test.js +42 -0
- package/package.json +12 -6
- package/src/cli.test.ts +324 -0
- package/src/cli.ts +145 -13
- package/src/embeddings.ts +4 -2
- package/src/index.ts +1 -1
- package/src/indexer.test.ts +262 -0
- package/src/indexer.ts +43 -10
- package/src/search.test.ts +49 -0
- package/src/search.ts +87 -8
- package/vitest.config.ts +12 -0
package/src/cli.test.ts
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
// parseArgs is not exported, so we re-implement the parsing logic for testing.
|
|
4
|
+
// This validates that the arg parsing contract is correct.
|
|
5
|
+
|
|
6
|
+
interface Args {
|
|
7
|
+
command: string;
|
|
8
|
+
query: string;
|
|
9
|
+
file: string;
|
|
10
|
+
dir: string;
|
|
11
|
+
teammate?: string;
|
|
12
|
+
results: number;
|
|
13
|
+
maxChunks?: number;
|
|
14
|
+
maxTokens?: number;
|
|
15
|
+
recencyDepth?: number;
|
|
16
|
+
typedMemoryBoost?: number;
|
|
17
|
+
model?: string;
|
|
18
|
+
json: boolean;
|
|
19
|
+
sync: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseArgs(argv: string[]): Args {
|
|
23
|
+
const args: Args = {
|
|
24
|
+
command: "",
|
|
25
|
+
query: "",
|
|
26
|
+
file: "",
|
|
27
|
+
dir: "./.teammates",
|
|
28
|
+
results: 5,
|
|
29
|
+
json: false,
|
|
30
|
+
sync: true,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
let i = 0;
|
|
34
|
+
while (
|
|
35
|
+
i < argv.length &&
|
|
36
|
+
(argv[i].includes("node") ||
|
|
37
|
+
argv[i].includes("teammates-recall") ||
|
|
38
|
+
argv[i].endsWith(".js"))
|
|
39
|
+
) {
|
|
40
|
+
i++;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (i < argv.length && !argv[i].startsWith("-")) {
|
|
44
|
+
args.command = argv[i++];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (
|
|
48
|
+
args.command === "search" &&
|
|
49
|
+
i < argv.length &&
|
|
50
|
+
!argv[i].startsWith("-")
|
|
51
|
+
) {
|
|
52
|
+
args.query = argv[i++];
|
|
53
|
+
} else if (
|
|
54
|
+
args.command === "add" &&
|
|
55
|
+
i < argv.length &&
|
|
56
|
+
!argv[i].startsWith("-")
|
|
57
|
+
) {
|
|
58
|
+
args.file = argv[i++];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
while (i < argv.length) {
|
|
62
|
+
const arg = argv[i++];
|
|
63
|
+
switch (arg) {
|
|
64
|
+
case "--dir":
|
|
65
|
+
args.dir = argv[i++];
|
|
66
|
+
break;
|
|
67
|
+
case "--teammate":
|
|
68
|
+
args.teammate = argv[i++];
|
|
69
|
+
break;
|
|
70
|
+
case "--results":
|
|
71
|
+
args.results = parseInt(argv[i++], 10);
|
|
72
|
+
break;
|
|
73
|
+
case "--model":
|
|
74
|
+
args.model = argv[i++];
|
|
75
|
+
break;
|
|
76
|
+
case "--max-chunks":
|
|
77
|
+
args.maxChunks = parseInt(argv[i++], 10);
|
|
78
|
+
break;
|
|
79
|
+
case "--max-tokens":
|
|
80
|
+
args.maxTokens = parseInt(argv[i++], 10);
|
|
81
|
+
break;
|
|
82
|
+
case "--recency-depth":
|
|
83
|
+
args.recencyDepth = parseInt(argv[i++], 10);
|
|
84
|
+
break;
|
|
85
|
+
case "--typed-memory-boost":
|
|
86
|
+
args.typedMemoryBoost = parseFloat(argv[i++]);
|
|
87
|
+
break;
|
|
88
|
+
case "--no-sync":
|
|
89
|
+
args.sync = false;
|
|
90
|
+
break;
|
|
91
|
+
case "--json":
|
|
92
|
+
args.json = true;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return args;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
describe("parseArgs", () => {
|
|
101
|
+
it("parses search command with query", () => {
|
|
102
|
+
const args = parseArgs(["node", "cli.js", "search", "hello world"]);
|
|
103
|
+
expect(args.command).toBe("search");
|
|
104
|
+
expect(args.query).toBe("hello world");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("parses add command with file path", () => {
|
|
108
|
+
const args = parseArgs([
|
|
109
|
+
"node",
|
|
110
|
+
"cli.js",
|
|
111
|
+
"add",
|
|
112
|
+
"memory/foo.md",
|
|
113
|
+
"--teammate",
|
|
114
|
+
"beacon",
|
|
115
|
+
]);
|
|
116
|
+
expect(args.command).toBe("add");
|
|
117
|
+
expect(args.file).toBe("memory/foo.md");
|
|
118
|
+
expect(args.teammate).toBe("beacon");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("parses index command", () => {
|
|
122
|
+
const args = parseArgs(["node", "cli.js", "index"]);
|
|
123
|
+
expect(args.command).toBe("index");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("parses sync command", () => {
|
|
127
|
+
const args = parseArgs(["node", "cli.js", "sync"]);
|
|
128
|
+
expect(args.command).toBe("sync");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("parses status command", () => {
|
|
132
|
+
const args = parseArgs(["node", "cli.js", "status"]);
|
|
133
|
+
expect(args.command).toBe("status");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("parses watch command", () => {
|
|
137
|
+
const args = parseArgs(["node", "cli.js", "watch"]);
|
|
138
|
+
expect(args.command).toBe("watch");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("defaults dir to ./.teammates", () => {
|
|
142
|
+
const args = parseArgs(["node", "cli.js", "index"]);
|
|
143
|
+
expect(args.dir).toBe("./.teammates");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("parses --dir flag", () => {
|
|
147
|
+
const args = parseArgs([
|
|
148
|
+
"node",
|
|
149
|
+
"cli.js",
|
|
150
|
+
"index",
|
|
151
|
+
"--dir",
|
|
152
|
+
"/path/to/.teammates",
|
|
153
|
+
]);
|
|
154
|
+
expect(args.dir).toBe("/path/to/.teammates");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("parses --teammate flag", () => {
|
|
158
|
+
const args = parseArgs([
|
|
159
|
+
"node",
|
|
160
|
+
"cli.js",
|
|
161
|
+
"search",
|
|
162
|
+
"query",
|
|
163
|
+
"--teammate",
|
|
164
|
+
"scribe",
|
|
165
|
+
]);
|
|
166
|
+
expect(args.teammate).toBe("scribe");
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("parses --results flag", () => {
|
|
170
|
+
const args = parseArgs([
|
|
171
|
+
"node",
|
|
172
|
+
"cli.js",
|
|
173
|
+
"search",
|
|
174
|
+
"query",
|
|
175
|
+
"--results",
|
|
176
|
+
"10",
|
|
177
|
+
]);
|
|
178
|
+
expect(args.results).toBe(10);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("parses --model flag", () => {
|
|
182
|
+
const args = parseArgs([
|
|
183
|
+
"node",
|
|
184
|
+
"cli.js",
|
|
185
|
+
"index",
|
|
186
|
+
"--model",
|
|
187
|
+
"custom/model",
|
|
188
|
+
]);
|
|
189
|
+
expect(args.model).toBe("custom/model");
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("parses --json flag", () => {
|
|
193
|
+
const args = parseArgs(["node", "cli.js", "status", "--json"]);
|
|
194
|
+
expect(args.json).toBe(true);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("defaults json to false", () => {
|
|
198
|
+
const args = parseArgs(["node", "cli.js", "status"]);
|
|
199
|
+
expect(args.json).toBe(false);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("parses --no-sync flag", () => {
|
|
203
|
+
const args = parseArgs(["node", "cli.js", "search", "query", "--no-sync"]);
|
|
204
|
+
expect(args.sync).toBe(false);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("defaults sync to true", () => {
|
|
208
|
+
const args = parseArgs(["node", "cli.js", "search", "query"]);
|
|
209
|
+
expect(args.sync).toBe(true);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("defaults results to 5", () => {
|
|
213
|
+
const args = parseArgs(["node", "cli.js", "search", "query"]);
|
|
214
|
+
expect(args.results).toBe(5);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("returns empty command for no args", () => {
|
|
218
|
+
const args = parseArgs(["node", "cli.js"]);
|
|
219
|
+
expect(args.command).toBe("");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("handles multiple flags together", () => {
|
|
223
|
+
const args = parseArgs([
|
|
224
|
+
"node",
|
|
225
|
+
"cli.js",
|
|
226
|
+
"search",
|
|
227
|
+
"my query",
|
|
228
|
+
"--dir",
|
|
229
|
+
"/tmp/.teammates",
|
|
230
|
+
"--teammate",
|
|
231
|
+
"beacon",
|
|
232
|
+
"--results",
|
|
233
|
+
"3",
|
|
234
|
+
"--json",
|
|
235
|
+
"--no-sync",
|
|
236
|
+
]);
|
|
237
|
+
expect(args.command).toBe("search");
|
|
238
|
+
expect(args.query).toBe("my query");
|
|
239
|
+
expect(args.dir).toBe("/tmp/.teammates");
|
|
240
|
+
expect(args.teammate).toBe("beacon");
|
|
241
|
+
expect(args.results).toBe(3);
|
|
242
|
+
expect(args.json).toBe(true);
|
|
243
|
+
expect(args.sync).toBe(false);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("parses --max-chunks flag", () => {
|
|
247
|
+
const args = parseArgs([
|
|
248
|
+
"node",
|
|
249
|
+
"cli.js",
|
|
250
|
+
"search",
|
|
251
|
+
"query",
|
|
252
|
+
"--max-chunks",
|
|
253
|
+
"7",
|
|
254
|
+
]);
|
|
255
|
+
expect(args.maxChunks).toBe(7);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("parses --max-tokens flag", () => {
|
|
259
|
+
const args = parseArgs([
|
|
260
|
+
"node",
|
|
261
|
+
"cli.js",
|
|
262
|
+
"search",
|
|
263
|
+
"query",
|
|
264
|
+
"--max-tokens",
|
|
265
|
+
"1000",
|
|
266
|
+
]);
|
|
267
|
+
expect(args.maxTokens).toBe(1000);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("parses --recency-depth flag", () => {
|
|
271
|
+
const args = parseArgs([
|
|
272
|
+
"node",
|
|
273
|
+
"cli.js",
|
|
274
|
+
"search",
|
|
275
|
+
"query",
|
|
276
|
+
"--recency-depth",
|
|
277
|
+
"4",
|
|
278
|
+
]);
|
|
279
|
+
expect(args.recencyDepth).toBe(4);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it("parses --typed-memory-boost flag", () => {
|
|
283
|
+
const args = parseArgs([
|
|
284
|
+
"node",
|
|
285
|
+
"cli.js",
|
|
286
|
+
"search",
|
|
287
|
+
"query",
|
|
288
|
+
"--typed-memory-boost",
|
|
289
|
+
"1.5",
|
|
290
|
+
]);
|
|
291
|
+
expect(args.typedMemoryBoost).toBe(1.5);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("handles multiple new search flags together", () => {
|
|
295
|
+
const args = parseArgs([
|
|
296
|
+
"node",
|
|
297
|
+
"cli.js",
|
|
298
|
+
"search",
|
|
299
|
+
"my query",
|
|
300
|
+
"--max-chunks",
|
|
301
|
+
"5",
|
|
302
|
+
"--max-tokens",
|
|
303
|
+
"800",
|
|
304
|
+
"--recency-depth",
|
|
305
|
+
"3",
|
|
306
|
+
"--typed-memory-boost",
|
|
307
|
+
"2.0",
|
|
308
|
+
]);
|
|
309
|
+
expect(args.command).toBe("search");
|
|
310
|
+
expect(args.query).toBe("my query");
|
|
311
|
+
expect(args.maxChunks).toBe(5);
|
|
312
|
+
expect(args.maxTokens).toBe(800);
|
|
313
|
+
expect(args.recencyDepth).toBe(3);
|
|
314
|
+
expect(args.typedMemoryBoost).toBe(2.0);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("leaves new flags undefined when not provided", () => {
|
|
318
|
+
const args = parseArgs(["node", "cli.js", "search", "query"]);
|
|
319
|
+
expect(args.maxChunks).toBeUndefined();
|
|
320
|
+
expect(args.maxTokens).toBeUndefined();
|
|
321
|
+
expect(args.recencyDepth).toBeUndefined();
|
|
322
|
+
expect(args.typedMemoryBoost).toBeUndefined();
|
|
323
|
+
});
|
|
324
|
+
});
|
package/src/cli.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { type FSWatcher, watch as fsWatch } from "node:fs";
|
|
4
4
|
import * as fs from "node:fs/promises";
|
|
5
|
+
import * as path from "node:path";
|
|
5
6
|
import { Indexer } from "./indexer.js";
|
|
6
7
|
import { search } from "./search.js";
|
|
7
8
|
|
|
@@ -14,15 +15,20 @@ Usage:
|
|
|
14
15
|
teammates-recall add <file> [options] Add a single file to a teammate's index
|
|
15
16
|
teammates-recall search <query> [options] Search teammate memories (auto-syncs)
|
|
16
17
|
teammates-recall status [options] Show index status
|
|
18
|
+
teammates-recall watch [options] Watch for changes and auto-sync
|
|
17
19
|
|
|
18
20
|
Options:
|
|
19
|
-
--dir <path>
|
|
20
|
-
--teammate <name>
|
|
21
|
-
--results <n>
|
|
22
|
-
--
|
|
23
|
-
--
|
|
24
|
-
--
|
|
25
|
-
--
|
|
21
|
+
--dir <path> Path to .teammates directory (default: ./.teammates)
|
|
22
|
+
--teammate <name> Limit to a specific teammate
|
|
23
|
+
--results <n> Max results (default: 5)
|
|
24
|
+
--max-chunks <n> Max chunks per document (default: 3)
|
|
25
|
+
--max-tokens <n> Max tokens per section (default: 500)
|
|
26
|
+
--recency-depth <n> Number of recent weekly summaries to include (default: 2)
|
|
27
|
+
--typed-memory-boost <n> Relevance boost for typed memories (default: 1.2)
|
|
28
|
+
--model <name> Embedding model (default: Xenova/all-MiniLM-L6-v2)
|
|
29
|
+
--no-sync Skip auto-sync before search
|
|
30
|
+
--json Output as JSON
|
|
31
|
+
--help Show this help
|
|
26
32
|
`.trim();
|
|
27
33
|
|
|
28
34
|
interface Args {
|
|
@@ -32,6 +38,10 @@ interface Args {
|
|
|
32
38
|
dir: string;
|
|
33
39
|
teammate?: string;
|
|
34
40
|
results: number;
|
|
41
|
+
maxChunks?: number;
|
|
42
|
+
maxTokens?: number;
|
|
43
|
+
recencyDepth?: number;
|
|
44
|
+
typedMemoryBoost?: number;
|
|
35
45
|
model?: string;
|
|
36
46
|
json: boolean;
|
|
37
47
|
sync: boolean;
|
|
@@ -50,7 +60,12 @@ function parseArgs(argv: string[]): Args {
|
|
|
50
60
|
|
|
51
61
|
let i = 0;
|
|
52
62
|
// Skip node and script path
|
|
53
|
-
while (
|
|
63
|
+
while (
|
|
64
|
+
i < argv.length &&
|
|
65
|
+
(argv[i].includes("node") ||
|
|
66
|
+
argv[i].includes("teammates-recall") ||
|
|
67
|
+
argv[i].endsWith(".js"))
|
|
68
|
+
) {
|
|
54
69
|
i++;
|
|
55
70
|
}
|
|
56
71
|
|
|
@@ -59,9 +74,17 @@ function parseArgs(argv: string[]): Args {
|
|
|
59
74
|
}
|
|
60
75
|
|
|
61
76
|
// For search, next non-flag arg is the query; for add, it's the file path
|
|
62
|
-
if (
|
|
77
|
+
if (
|
|
78
|
+
args.command === "search" &&
|
|
79
|
+
i < argv.length &&
|
|
80
|
+
!argv[i].startsWith("-")
|
|
81
|
+
) {
|
|
63
82
|
args.query = argv[i++];
|
|
64
|
-
} else if (
|
|
83
|
+
} else if (
|
|
84
|
+
args.command === "add" &&
|
|
85
|
+
i < argv.length &&
|
|
86
|
+
!argv[i].startsWith("-")
|
|
87
|
+
) {
|
|
65
88
|
args.file = argv[i++];
|
|
66
89
|
}
|
|
67
90
|
|
|
@@ -80,6 +103,18 @@ function parseArgs(argv: string[]): Args {
|
|
|
80
103
|
case "--model":
|
|
81
104
|
args.model = argv[i++];
|
|
82
105
|
break;
|
|
106
|
+
case "--max-chunks":
|
|
107
|
+
args.maxChunks = parseInt(argv[i++], 10);
|
|
108
|
+
break;
|
|
109
|
+
case "--max-tokens":
|
|
110
|
+
args.maxTokens = parseInt(argv[i++], 10);
|
|
111
|
+
break;
|
|
112
|
+
case "--recency-depth":
|
|
113
|
+
args.recencyDepth = parseInt(argv[i++], 10);
|
|
114
|
+
break;
|
|
115
|
+
case "--typed-memory-boost":
|
|
116
|
+
args.typedMemoryBoost = parseFloat(argv[i++]);
|
|
117
|
+
break;
|
|
83
118
|
case "--no-sync":
|
|
84
119
|
args.sync = false;
|
|
85
120
|
break;
|
|
@@ -177,7 +212,13 @@ async function cmdAdd(args: Args): Promise<void> {
|
|
|
177
212
|
await indexer.upsertFile(args.teammate, args.file);
|
|
178
213
|
|
|
179
214
|
if (args.json) {
|
|
180
|
-
console.log(
|
|
215
|
+
console.log(
|
|
216
|
+
JSON.stringify({
|
|
217
|
+
teammate: args.teammate,
|
|
218
|
+
file: args.file,
|
|
219
|
+
status: "ok",
|
|
220
|
+
}),
|
|
221
|
+
);
|
|
181
222
|
} else {
|
|
182
223
|
console.log(`Added ${args.file} to ${args.teammate}'s index`);
|
|
183
224
|
}
|
|
@@ -195,6 +236,10 @@ async function cmdSearch(args: Args): Promise<void> {
|
|
|
195
236
|
teammatesDir,
|
|
196
237
|
teammate: args.teammate,
|
|
197
238
|
maxResults: args.results,
|
|
239
|
+
maxChunks: args.maxChunks,
|
|
240
|
+
maxTokens: args.maxTokens,
|
|
241
|
+
recencyDepth: args.recencyDepth,
|
|
242
|
+
typedMemoryBoost: args.typedMemoryBoost,
|
|
198
243
|
model: args.model,
|
|
199
244
|
skipSync: !args.sync,
|
|
200
245
|
});
|
|
@@ -207,7 +252,9 @@ async function cmdSearch(args: Args): Promise<void> {
|
|
|
207
252
|
return;
|
|
208
253
|
}
|
|
209
254
|
for (const result of results) {
|
|
210
|
-
console.log(
|
|
255
|
+
console.log(
|
|
256
|
+
`--- ${result.teammate} | ${result.uri} (score: ${result.score.toFixed(3)}) ---`,
|
|
257
|
+
);
|
|
211
258
|
console.log(result.text);
|
|
212
259
|
console.log();
|
|
213
260
|
}
|
|
@@ -244,6 +291,88 @@ async function cmdStatus(args: Args): Promise<void> {
|
|
|
244
291
|
}
|
|
245
292
|
}
|
|
246
293
|
|
|
294
|
+
async function cmdWatch(args: Args): Promise<void> {
|
|
295
|
+
const teammatesDir = await resolveTeammatesDir(args.dir);
|
|
296
|
+
const indexer = new Indexer({ teammatesDir, model: args.model });
|
|
297
|
+
|
|
298
|
+
// Initial sync
|
|
299
|
+
console.error("Initial sync...");
|
|
300
|
+
const results = await indexer.syncAll();
|
|
301
|
+
for (const [teammate, count] of results) {
|
|
302
|
+
console.error(` ${teammate}: ${count} files`);
|
|
303
|
+
}
|
|
304
|
+
console.error("Watching for changes...");
|
|
305
|
+
|
|
306
|
+
if (args.json) {
|
|
307
|
+
console.log(JSON.stringify({ status: "watching", dir: teammatesDir }));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Debounce: collect changes, sync after 2s of quiet
|
|
311
|
+
let syncTimer: ReturnType<typeof setTimeout> | null = null;
|
|
312
|
+
const pendingTeammates = new Set<string>();
|
|
313
|
+
|
|
314
|
+
const scheduleSync = (teammate: string) => {
|
|
315
|
+
pendingTeammates.add(teammate);
|
|
316
|
+
if (syncTimer) clearTimeout(syncTimer);
|
|
317
|
+
syncTimer = setTimeout(async () => {
|
|
318
|
+
for (const t of pendingTeammates) {
|
|
319
|
+
try {
|
|
320
|
+
const count = await indexer.syncTeammate(t);
|
|
321
|
+
if (args.json) {
|
|
322
|
+
console.log(
|
|
323
|
+
JSON.stringify({ event: "sync", teammate: t, files: count }),
|
|
324
|
+
);
|
|
325
|
+
} else {
|
|
326
|
+
console.error(` synced ${t}: ${count} files`);
|
|
327
|
+
}
|
|
328
|
+
} catch (err: unknown) {
|
|
329
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
330
|
+
console.error(` error syncing ${t}: ${msg}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
pendingTeammates.clear();
|
|
334
|
+
}, 2000);
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
// Watch each teammate's directory for changes
|
|
338
|
+
const watchers: FSWatcher[] = [];
|
|
339
|
+
const teammates = await indexer.discoverTeammates();
|
|
340
|
+
|
|
341
|
+
for (const teammate of teammates) {
|
|
342
|
+
const teammateDir = path.join(teammatesDir, teammate);
|
|
343
|
+
try {
|
|
344
|
+
const watcher = fsWatch(
|
|
345
|
+
teammateDir,
|
|
346
|
+
{ recursive: true },
|
|
347
|
+
(_eventType, filename) => {
|
|
348
|
+
if (!filename) return;
|
|
349
|
+
// Only care about .md files, skip .index/
|
|
350
|
+
if (!filename.endsWith(".md") || filename.includes(".index")) return;
|
|
351
|
+
scheduleSync(teammate);
|
|
352
|
+
},
|
|
353
|
+
);
|
|
354
|
+
watchers.push(watcher);
|
|
355
|
+
} catch {
|
|
356
|
+
console.error(` warning: could not watch ${teammate}/`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Keep alive until killed
|
|
361
|
+
const shutdown = () => {
|
|
362
|
+
if (syncTimer) clearTimeout(syncTimer);
|
|
363
|
+
for (const w of watchers) w.close();
|
|
364
|
+
if (args.json) {
|
|
365
|
+
console.log(JSON.stringify({ status: "stopped" }));
|
|
366
|
+
}
|
|
367
|
+
process.exit(0);
|
|
368
|
+
};
|
|
369
|
+
process.on("SIGTERM", shutdown);
|
|
370
|
+
process.on("SIGINT", shutdown);
|
|
371
|
+
|
|
372
|
+
// Block forever
|
|
373
|
+
await new Promise(() => {});
|
|
374
|
+
}
|
|
375
|
+
|
|
247
376
|
async function main(): Promise<void> {
|
|
248
377
|
const args = parseArgs(process.argv);
|
|
249
378
|
|
|
@@ -263,6 +392,9 @@ async function main(): Promise<void> {
|
|
|
263
392
|
case "status":
|
|
264
393
|
await cmdStatus(args);
|
|
265
394
|
break;
|
|
395
|
+
case "watch":
|
|
396
|
+
await cmdWatch(args);
|
|
397
|
+
break;
|
|
266
398
|
default:
|
|
267
399
|
console.log(HELP);
|
|
268
400
|
process.exit(args.command ? 1 : 0);
|
package/src/embeddings.ts
CHANGED
|
@@ -17,11 +17,13 @@ export class LocalEmbeddings implements EmbeddingsModel {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
async createEmbeddings(
|
|
20
|
-
inputs: string | string[]
|
|
20
|
+
inputs: string | string[],
|
|
21
21
|
): Promise<EmbeddingsResponse> {
|
|
22
22
|
try {
|
|
23
23
|
const extractor = await this._getExtractor();
|
|
24
|
-
const texts = (Array.isArray(inputs) ? inputs : [inputs]).filter(
|
|
24
|
+
const texts = (Array.isArray(inputs) ? inputs : [inputs]).filter(
|
|
25
|
+
(t) => t.trim().length > 0,
|
|
26
|
+
);
|
|
25
27
|
if (texts.length === 0) {
|
|
26
28
|
return { status: "success", output: [] };
|
|
27
29
|
}
|
package/src/index.ts
CHANGED