@soederpop/luca 0.0.6 → 0.0.8
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/CLAUDE.md +10 -1
- package/RUNME.md +56 -0
- package/bun.lock +1 -1
- package/commands/build-bootstrap.ts +78 -0
- package/commands/build-scaffolds.ts +24 -2
- package/commands/try-all-challenges.ts +543 -0
- package/commands/try-challenge.ts +100 -0
- package/docs/README.md +52 -80
- package/docs/TABLE-OF-CONTENTS.md +82 -51
- package/docs/apis/clients/elevenlabs.md +232 -8
- package/docs/apis/clients/graph.md +59 -8
- package/docs/apis/clients/openai.md +362 -2
- package/docs/apis/clients/rest.md +122 -2
- package/docs/apis/clients/websocket.md +71 -17
- package/docs/apis/features/agi/assistant.md +9 -3
- package/docs/apis/features/agi/assistants-manager.md +2 -2
- package/docs/apis/features/agi/claude-code.md +153 -14
- package/docs/apis/features/agi/conversation-history.md +15 -3
- package/docs/apis/features/agi/conversation.md +133 -20
- package/docs/apis/features/agi/openai-codex.md +90 -12
- package/docs/apis/features/agi/skills-library.md +23 -5
- package/docs/apis/features/node/container-link.md +59 -0
- package/docs/apis/features/node/content-db.md +1 -1
- package/docs/apis/features/node/disk-cache.md +1 -1
- package/docs/apis/features/node/dns.md +1 -0
- package/docs/apis/features/node/docker.md +2 -1
- package/docs/apis/features/node/esbuild.md +4 -3
- package/docs/apis/features/node/file-manager.md +13 -4
- package/docs/apis/features/node/fs.md +726 -171
- package/docs/apis/features/node/git.md +1 -0
- package/docs/apis/features/node/google-auth.md +23 -4
- package/docs/apis/features/node/google-calendar.md +14 -2
- package/docs/apis/features/node/google-docs.md +15 -2
- package/docs/apis/features/node/google-drive.md +21 -3
- package/docs/apis/features/node/google-sheets.md +14 -2
- package/docs/apis/features/node/grep.md +2 -0
- package/docs/apis/features/node/helpers.md +29 -0
- package/docs/apis/features/node/ink.md +2 -2
- package/docs/apis/features/node/networking.md +39 -4
- package/docs/apis/features/node/os.md +28 -0
- package/docs/apis/features/node/postgres.md +26 -4
- package/docs/apis/features/node/proc.md +37 -28
- package/docs/apis/features/node/process-manager.md +33 -5
- package/docs/apis/features/node/repl.md +1 -1
- package/docs/apis/features/node/runpod.md +1 -0
- package/docs/apis/features/node/secure-shell.md +7 -0
- package/docs/apis/features/node/semantic-search.md +12 -5
- package/docs/apis/features/node/sqlite.md +26 -4
- package/docs/apis/features/node/telegram.md +30 -5
- package/docs/apis/features/node/tts.md +17 -2
- package/docs/apis/features/node/ui.md +1 -1
- package/docs/apis/features/node/vault.md +4 -9
- package/docs/apis/features/node/vm.md +3 -12
- package/docs/apis/features/node/window-manager.md +128 -20
- package/docs/apis/features/web/asset-loader.md +13 -1
- package/docs/apis/features/web/container-link.md +59 -0
- package/docs/apis/features/web/esbuild.md +4 -3
- package/docs/apis/features/web/helpers.md +29 -0
- package/docs/apis/features/web/network.md +16 -2
- package/docs/apis/features/web/speech.md +16 -2
- package/docs/apis/features/web/vault.md +4 -9
- package/docs/apis/features/web/vm.md +3 -12
- package/docs/apis/features/web/voice.md +18 -1
- package/docs/apis/servers/express.md +18 -2
- package/docs/apis/servers/mcp.md +29 -4
- package/docs/apis/servers/websocket.md +34 -6
- package/docs/bootstrap/CLAUDE.md +100 -0
- package/docs/bootstrap/SKILL.md +222 -0
- package/docs/bootstrap/templates/about-command.ts +41 -0
- package/docs/bootstrap/templates/docs-models.ts +22 -0
- package/docs/bootstrap/templates/docs-readme.md +43 -0
- package/docs/bootstrap/templates/example-feature.ts +53 -0
- package/docs/bootstrap/templates/health-endpoint.ts +15 -0
- package/docs/bootstrap/templates/luca-cli.ts +25 -0
- package/docs/bootstrap/templates/runme.md +54 -0
- package/docs/challenges/caching-proxy.md +16 -0
- package/docs/challenges/content-db-round-trip.md +14 -0
- package/docs/challenges/custom-command.md +9 -0
- package/docs/challenges/file-watcher-pipeline.md +11 -0
- package/docs/challenges/grep-audit-report.md +15 -0
- package/docs/challenges/multi-feature-dashboard.md +14 -0
- package/docs/challenges/process-orchestrator.md +17 -0
- package/docs/challenges/rest-api-server-with-client.md +12 -0
- package/docs/challenges/script-runner-with-vm.md +11 -0
- package/docs/challenges/simple-rest-api.md +15 -0
- package/docs/challenges/websocket-serve-and-client.md +11 -0
- package/docs/challenges/yaml-config-system.md +14 -0
- package/docs/command-system-overhaul.md +94 -0
- package/docs/examples/assistant/CORE.md +18 -0
- package/docs/examples/assistant/hooks.ts +3 -0
- package/docs/examples/assistant/tools.ts +10 -0
- package/docs/examples/window-manager-layouts.md +180 -0
- package/docs/in-memory-fs.md +4 -0
- package/docs/models.ts +13 -10
- package/docs/philosophy.md +4 -3
- package/docs/reports/console-hmr-design.md +170 -0
- package/docs/reports/helper-semantic-search.md +72 -0
- package/docs/scaffolds/client.md +29 -20
- package/docs/scaffolds/command.md +64 -50
- package/docs/scaffolds/endpoint.md +31 -36
- package/docs/scaffolds/feature.md +28 -18
- package/docs/scaffolds/selector.md +91 -0
- package/docs/scaffolds/server.md +18 -9
- package/docs/selectors.md +115 -0
- package/docs/sessions/custom-command/attempt-log-2.md +195 -0
- package/docs/sessions/file-watcher-pipeline/attempt-log-1.md +728 -0
- package/docs/sessions/file-watcher-pipeline/attempt-log-2.md +555 -0
- package/docs/sessions/grep-audit-report/attempt-log-1.md +289 -0
- package/docs/sessions/multi-feature-dashboard/attempt-log-2.md +679 -0
- package/docs/sessions/rest-api-server-with-client/attempt-log-1.md +1 -0
- package/docs/sessions/rest-api-server-with-client/attempt-log-3.md +920 -0
- package/docs/sessions/simple-rest-api/attempt-log-1.md +593 -0
- package/docs/sessions/websocket-serve-and-client/attempt-log-2.md +995 -0
- package/docs/tutorials/00-bootstrap.md +148 -0
- package/docs/tutorials/07-endpoints.md +7 -7
- package/docs/tutorials/08-commands.md +153 -72
- package/luca.cli.ts +3 -0
- package/package.json +6 -5
- package/public/index.html +1430 -0
- package/scripts/examples/using-ollama.ts +2 -1
- package/scripts/update-introspection-data.ts +2 -2
- package/src/agi/endpoints/experts.ts +1 -1
- package/src/agi/features/assistant.ts +7 -0
- package/src/agi/features/assistants-manager.ts +5 -5
- package/src/agi/features/claude-code.ts +263 -3
- package/src/agi/features/conversation-history.ts +7 -1
- package/src/agi/features/conversation.ts +26 -3
- package/src/agi/features/openai-codex.ts +26 -2
- package/src/agi/features/openapi.ts +6 -1
- package/src/agi/features/skills-library.ts +9 -1
- package/src/bootstrap/generated.ts +595 -0
- package/src/cli/cli.ts +64 -21
- package/src/client.ts +23 -357
- package/src/clients/civitai/index.ts +1 -1
- package/src/clients/client-template.ts +1 -1
- package/src/clients/comfyui/index.ts +13 -2
- package/src/clients/elevenlabs/index.ts +2 -1
- package/src/clients/graph.ts +87 -0
- package/src/clients/openai/index.ts +10 -1
- package/src/clients/rest.ts +207 -0
- package/src/clients/websocket.ts +176 -0
- package/src/command.ts +281 -34
- package/src/commands/bootstrap.ts +185 -0
- package/src/commands/chat.ts +5 -4
- package/src/commands/describe.ts +341 -4
- package/src/commands/help.ts +35 -9
- package/src/commands/index.ts +3 -0
- package/src/commands/introspect.ts +92 -2
- package/src/commands/prompt.ts +5 -6
- package/src/commands/run.ts +75 -10
- package/src/commands/save-api-docs.ts +49 -0
- package/src/commands/scaffold.ts +169 -23
- package/src/commands/select.ts +94 -0
- package/src/commands/serve.ts +10 -1
- package/src/container.ts +15 -0
- package/src/endpoint.ts +19 -0
- package/src/graft.ts +181 -0
- package/src/introspection/generated.agi.ts +12458 -8968
- package/src/introspection/generated.node.ts +10573 -7145
- package/src/introspection/generated.web.ts +1 -1
- package/src/introspection/index.ts +26 -0
- package/src/node/container.ts +6 -7
- package/src/node/features/content-db.ts +49 -2
- package/src/node/features/disk-cache.ts +16 -9
- package/src/node/features/dns.ts +16 -3
- package/src/node/features/docker.ts +16 -4
- package/src/node/features/esbuild.ts +22 -2
- package/src/node/features/file-manager.ts +184 -29
- package/src/node/features/fs.ts +704 -248
- package/src/node/features/git.ts +21 -8
- package/src/node/features/grep.ts +23 -3
- package/src/node/features/helpers.ts +372 -43
- package/src/node/features/networking.ts +39 -4
- package/src/node/features/opener.ts +28 -15
- package/src/node/features/os.ts +76 -0
- package/src/node/features/port-exposer.ts +11 -1
- package/src/node/features/postgres.ts +17 -1
- package/src/node/features/proc.ts +4 -1
- package/src/node/features/python.ts +63 -14
- package/src/node/features/repl.ts +11 -7
- package/src/node/features/runpod.ts +16 -3
- package/src/node/features/secure-shell.ts +27 -2
- package/src/node/features/semantic-search.ts +12 -1
- package/src/node/features/ui.ts +5 -69
- package/src/node/features/vm.ts +17 -0
- package/src/node/features/window-manager.ts +68 -20
- package/src/node.ts +5 -0
- package/src/scaffolds/generated.ts +492 -290
- package/src/scaffolds/template.ts +9 -0
- package/src/schemas/base.ts +46 -5
- package/src/selector.ts +282 -0
- package/src/server.ts +11 -0
- package/src/servers/express.ts +27 -12
- package/src/servers/socket.ts +45 -11
- package/src/web/clients/socket.ts +4 -1
- package/src/web/container.ts +2 -1
- package/src/web/features/network.ts +7 -1
- package/src/web/features/voice-recognition.ts +16 -1
- package/test/clients-servers.test.ts +2 -1
- package/test/command.test.ts +267 -0
- package/test/vm-context.test.ts +146 -0
- package/test-integration/assistants-manager.test.ts +10 -20
- package/docs/apis/features/node/launcher-app-command-listener.md +0 -145
- package/docs/examples/launcher-app-command-listener.md +0 -120
- package/docs/tasks/web-container-helper-discovery.md +0 -71
- package/docs/todos.md +0 -1
- package/scripts/test-command-listener.ts +0 -123
- package/src/node/features/launcher-app-command-listener.ts +0 -389
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
|
-
import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
|
|
2
|
+
import { FeatureEventsSchema, FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
|
|
3
3
|
import { State } from "../../state.js";
|
|
4
4
|
import { Feature } from "../feature.js";
|
|
5
|
-
import { parse, relative } from "path";
|
|
6
|
-
import { statSync } from "fs";
|
|
5
|
+
import { parse, relative, join as pathJoin } from "path";
|
|
6
|
+
import { statSync, readFileSync, existsSync } from "fs";
|
|
7
7
|
import micromatch from "micromatch";
|
|
8
8
|
import { castArray } from "lodash-es";
|
|
9
9
|
import chokidar from "chokidar";
|
|
@@ -37,6 +37,15 @@ export const FileManagerOptionsSchema = FeatureOptionsSchema.extend({
|
|
|
37
37
|
exclude: z.union([z.string(), z.array(z.string())]).optional().describe('Glob patterns to exclude from file scanning'),
|
|
38
38
|
})
|
|
39
39
|
|
|
40
|
+
export const FileManagerEventsSchema = FeatureEventsSchema.extend({
|
|
41
|
+
'file:change': z.tuple([
|
|
42
|
+
z.object({
|
|
43
|
+
type: z.enum(['add', 'change', 'delete']).describe('The type of file change'),
|
|
44
|
+
path: z.string().describe('Absolute path to the changed file'),
|
|
45
|
+
}).describe('File change event payload'),
|
|
46
|
+
]).describe('Emitted when a watched file is added, changed, or deleted'),
|
|
47
|
+
}).describe('FileManager events')
|
|
48
|
+
|
|
40
49
|
export type FileManagerState = z.infer<typeof FileManagerStateSchema>
|
|
41
50
|
export type FileManagerOptions = z.infer<typeof FileManagerOptionsSchema>
|
|
42
51
|
|
|
@@ -61,6 +70,7 @@ export class FileManager<
|
|
|
61
70
|
static override shortcut = 'features.fileManager' as const
|
|
62
71
|
static override stateSchema = FileManagerStateSchema
|
|
63
72
|
static override optionsSchema = FileManagerOptionsSchema
|
|
73
|
+
static override eventsSchema = FileManagerEventsSchema
|
|
64
74
|
static { Feature.register(this, 'fileManager') }
|
|
65
75
|
|
|
66
76
|
files: State<Record<string, File>> = new State<Record<string, File>>({
|
|
@@ -135,6 +145,11 @@ export class FileManager<
|
|
|
135
145
|
return !!this.state.get("watching");
|
|
136
146
|
}
|
|
137
147
|
|
|
148
|
+
/** Returns the list of directories currently being watched. */
|
|
149
|
+
get watchedPaths(): string[] {
|
|
150
|
+
return this.state.get("watchedPaths") || [];
|
|
151
|
+
}
|
|
152
|
+
|
|
138
153
|
/**
|
|
139
154
|
* Starts the file manager and scans the files in the project.
|
|
140
155
|
* @param {object} [options={}] - Options for the file manager
|
|
@@ -154,7 +169,11 @@ export class FileManager<
|
|
|
154
169
|
}
|
|
155
170
|
|
|
156
171
|
try {
|
|
157
|
-
await this.
|
|
172
|
+
const loaded = await this.loadFromCache();
|
|
173
|
+
if (!loaded) {
|
|
174
|
+
await this.scanFiles(options);
|
|
175
|
+
await this.writeToCache();
|
|
176
|
+
}
|
|
158
177
|
} catch (error) {
|
|
159
178
|
console.error(error);
|
|
160
179
|
this.state.set("failed", true);
|
|
@@ -166,6 +185,117 @@ export class FileManager<
|
|
|
166
185
|
return this;
|
|
167
186
|
}
|
|
168
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Attempts to load the file index from disk cache.
|
|
190
|
+
* Only uses cache when in a clean git repo (sha hasn't changed, no dirty files).
|
|
191
|
+
* @returns true if cache was loaded successfully, false otherwise
|
|
192
|
+
*/
|
|
193
|
+
/**
|
|
194
|
+
* Reads the current git SHA by reading .git/HEAD directly,
|
|
195
|
+
* avoiding the ~19ms cost of shelling out to `git rev-parse HEAD`.
|
|
196
|
+
*/
|
|
197
|
+
private readGitShaFast(): string | null {
|
|
198
|
+
try {
|
|
199
|
+
const { git } = this.container;
|
|
200
|
+
if (!git.isRepo) return null;
|
|
201
|
+
|
|
202
|
+
const gitDir = pathJoin(git.repoRoot, '.git');
|
|
203
|
+
const head = readFileSync(pathJoin(gitDir, 'HEAD'), 'utf8').trim();
|
|
204
|
+
|
|
205
|
+
// Detached HEAD — already a sha
|
|
206
|
+
if (!head.startsWith('ref: ')) return head;
|
|
207
|
+
|
|
208
|
+
// Resolve the ref
|
|
209
|
+
const refPath = pathJoin(gitDir, head.slice(5));
|
|
210
|
+
if (existsSync(refPath)) {
|
|
211
|
+
return readFileSync(refPath, 'utf8').trim();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Packed refs fallback
|
|
215
|
+
const packedRefsPath = pathJoin(gitDir, 'packed-refs');
|
|
216
|
+
if (existsSync(packedRefsPath)) {
|
|
217
|
+
const ref = head.slice(5);
|
|
218
|
+
const packed = readFileSync(packedRefsPath, 'utf8');
|
|
219
|
+
const match = packed.match(new RegExp(`^([0-9a-f]{40}) ${ref}`, 'm'));
|
|
220
|
+
if (match) return match[1];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return null;
|
|
224
|
+
} catch {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private async loadFromCache(): Promise<boolean> {
|
|
230
|
+
try {
|
|
231
|
+
const sha = this.readGitShaFast();
|
|
232
|
+
if (!sha) return false;
|
|
233
|
+
|
|
234
|
+
const cache = this.container.feature('diskCache') as any;
|
|
235
|
+
const cacheKey = `file-index:${sha}`;
|
|
236
|
+
|
|
237
|
+
if (!(await cache.has(cacheKey))) return false;
|
|
238
|
+
|
|
239
|
+
const cached = await cache.get(cacheKey, true) as { dirs: Record<string, number>, files: Record<string, any> };
|
|
240
|
+
if (!cached?.files || !cached?.dirs) return false;
|
|
241
|
+
|
|
242
|
+
// Check if any directory mtime has changed — catches new/deleted/renamed files
|
|
243
|
+
for (const [dir, cachedMtimeMs] of Object.entries(cached.dirs)) {
|
|
244
|
+
try {
|
|
245
|
+
const current = statSync(dir).mtimeMs;
|
|
246
|
+
if (current !== cachedMtimeMs) return false;
|
|
247
|
+
} catch {
|
|
248
|
+
// Directory no longer exists
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
for (const [relativePath, file] of Object.entries(cached.files)) {
|
|
254
|
+
this.files.set(relativePath, {
|
|
255
|
+
...file as File,
|
|
256
|
+
modifiedAt: new Date((file as any).modifiedAt),
|
|
257
|
+
createdAt: new Date((file as any).createdAt),
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return true;
|
|
262
|
+
} catch {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Writes the current file index to disk cache, keyed by git sha.
|
|
269
|
+
* Stores directory mtimes alongside file data so the cache can be
|
|
270
|
+
* invalidated when files are added/removed without a new commit.
|
|
271
|
+
*/
|
|
272
|
+
private async writeToCache(): Promise<void> {
|
|
273
|
+
try {
|
|
274
|
+
const sha = this.readGitShaFast();
|
|
275
|
+
if (!sha) return;
|
|
276
|
+
|
|
277
|
+
const cache = this.container.feature('diskCache') as any;
|
|
278
|
+
const cacheKey = `file-index:${sha}`;
|
|
279
|
+
|
|
280
|
+
// Collect unique directories and their mtimes
|
|
281
|
+
const dirs: Record<string, number> = {};
|
|
282
|
+
const files: Record<string, any> = {};
|
|
283
|
+
|
|
284
|
+
for (const [key, file] of this.files.entries()) {
|
|
285
|
+
files[key] = file;
|
|
286
|
+
if (!dirs[file.dirname]) {
|
|
287
|
+
try {
|
|
288
|
+
dirs[file.dirname] = statSync(file.dirname).mtimeMs;
|
|
289
|
+
} catch {}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
await cache.set(cacheKey, { dirs, files });
|
|
294
|
+
} catch {
|
|
295
|
+
// Cache write failure is non-fatal
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
169
299
|
/**
|
|
170
300
|
* Scans the files in the project and updates the file manager state.
|
|
171
301
|
* @param {object} [options={}] - Options for the file manager
|
|
@@ -252,14 +382,33 @@ export class FileManager<
|
|
|
252
382
|
return this.watcher?.getWatched() || {};
|
|
253
383
|
}
|
|
254
384
|
|
|
255
|
-
/**
|
|
256
|
-
* Watches
|
|
385
|
+
/**
|
|
386
|
+
* Watches directories for file changes. Can be called multiple times to add
|
|
387
|
+
* more directories to an existing watcher. Tracks all watched paths in state.
|
|
388
|
+
*
|
|
389
|
+
* When called without `paths`, watches the project's `directoryIds` (default behavior).
|
|
390
|
+
* When called with `paths`, watches only those specific directories/globs.
|
|
391
|
+
* Subsequent calls add to the existing watcher — they never replace what's already watched.
|
|
392
|
+
*
|
|
257
393
|
* @param {object} [options={}] - Options for the file manager
|
|
394
|
+
* @param {string | string[]} [options.paths] - Specific directories or globs to watch. Defaults to project directoryIds.
|
|
258
395
|
* @param {string | string[]} [options.exclude] - The patterns to exclude from the watch
|
|
259
|
-
* @returns {Promise<void>}
|
|
396
|
+
* @returns {Promise<void>}
|
|
260
397
|
*/
|
|
261
|
-
async watch(options: { exclude?: string | string[] } = {}) {
|
|
262
|
-
|
|
398
|
+
async watch(options: { paths?: string | string[]; exclude?: string | string[] } = {}) {
|
|
399
|
+
const pathsToWatch = castArray(options.paths || this.directoryIds.map(id => this.container.paths.resolve(id)))
|
|
400
|
+
.map(p => this.container.paths.resolve(p));
|
|
401
|
+
|
|
402
|
+
// If already watching, just add the new paths
|
|
403
|
+
if (this.isWatching && this.watcher) {
|
|
404
|
+
const currentPaths: string[] = this.state.get("watchedPaths") || [];
|
|
405
|
+
const newPaths = pathsToWatch.filter(p => !currentPaths.includes(p));
|
|
406
|
+
|
|
407
|
+
if (newPaths.length) {
|
|
408
|
+
this.watcher.add(newPaths);
|
|
409
|
+
this.state.set("watchedPaths", [...currentPaths, ...newPaths]);
|
|
410
|
+
}
|
|
411
|
+
|
|
263
412
|
return;
|
|
264
413
|
}
|
|
265
414
|
|
|
@@ -273,11 +422,7 @@ export class FileManager<
|
|
|
273
422
|
|
|
274
423
|
exclude.push(...castArray(this.options.exclude!).filter((v) => v?.length));
|
|
275
424
|
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
const watcher = chokidar.watch(
|
|
279
|
-
this.directoryIds.map(id => this.container.paths.resolve(id))
|
|
280
|
-
, {
|
|
425
|
+
const watcher = chokidar.watch(pathsToWatch, {
|
|
281
426
|
ignoreInitial: true,
|
|
282
427
|
persistent: true,
|
|
283
428
|
ignored: [
|
|
@@ -292,17 +437,17 @@ export class FileManager<
|
|
|
292
437
|
|
|
293
438
|
watcher
|
|
294
439
|
.on("add", (path) => {
|
|
440
|
+
this.updateFile(path);
|
|
295
441
|
this.emit("file:change", {
|
|
296
442
|
type: "add",
|
|
297
443
|
path,
|
|
298
444
|
});
|
|
299
|
-
this.updateFile(path);
|
|
300
445
|
})
|
|
301
446
|
.on("change", (path) => {
|
|
302
447
|
this.updateFile(path);
|
|
303
448
|
this.emit("file:change", {
|
|
304
|
-
path,
|
|
305
449
|
type: "change",
|
|
450
|
+
path,
|
|
306
451
|
});
|
|
307
452
|
})
|
|
308
453
|
.on("unlink", (path) => {
|
|
@@ -315,6 +460,7 @@ export class FileManager<
|
|
|
315
460
|
|
|
316
461
|
watcher.on("ready", () => {
|
|
317
462
|
this.state.set("watching", true);
|
|
463
|
+
this.state.set("watchedPaths", pathsToWatch);
|
|
318
464
|
});
|
|
319
465
|
|
|
320
466
|
this.watcher = watcher;
|
|
@@ -328,26 +474,35 @@ export class FileManager<
|
|
|
328
474
|
if (this.watcher) {
|
|
329
475
|
this.watcher.close();
|
|
330
476
|
this.state.set("watching", false);
|
|
477
|
+
this.state.set("watchedPaths", []);
|
|
331
478
|
this.watcher = null;
|
|
332
479
|
}
|
|
333
480
|
}
|
|
334
481
|
|
|
335
482
|
async updateFile(path: string) {
|
|
336
|
-
// Reuse the logic from the scanFiles method to update a single file
|
|
337
483
|
const absolutePath = this.container.paths.resolve(path);
|
|
338
484
|
const { name, ext, dir } = parse(absolutePath);
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
485
|
+
|
|
486
|
+
try {
|
|
487
|
+
const stats = statSync(absolutePath);
|
|
488
|
+
this.files.set(path, {
|
|
489
|
+
dirname: dir,
|
|
490
|
+
absolutePath,
|
|
491
|
+
relativePath: path,
|
|
492
|
+
name,
|
|
493
|
+
extension: ext,
|
|
494
|
+
size: stats.size,
|
|
495
|
+
modifiedAt: stats.mtime,
|
|
496
|
+
createdAt: stats.birthtime,
|
|
497
|
+
});
|
|
498
|
+
} catch (err: any) {
|
|
499
|
+
// File may have been moved or deleted by an event handler — remove from index gracefully
|
|
500
|
+
if (err.code === 'ENOENT') {
|
|
501
|
+
this.files.delete(path);
|
|
502
|
+
} else {
|
|
503
|
+
throw err;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
351
506
|
}
|
|
352
507
|
|
|
353
508
|
async removeFile(path: string) {
|