@venturewild/workspace 0.1.0 → 0.1.2

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.
@@ -1,81 +1,86 @@
1
- // Component System inbox surfacing.
2
- // When `.wild/inbox.md` exists or changes, emit notifications.
3
- // Drives the "I noticed you imported X — want me to walk integration?" cue in chat.
4
-
5
- import fs from 'node:fs/promises';
6
- import { existsSync } from 'node:fs';
7
- import path from 'node:path';
8
- import chokidar from 'chokidar';
9
- import { EventEmitter } from 'node:events';
10
-
11
- export class InboxWatcher extends EventEmitter {
12
- constructor(workspaceDir) {
13
- super();
14
- this.workspaceDir = workspaceDir;
15
- this.inboxPath = path.join(workspaceDir, '.wild', 'inbox.md');
16
- this.installedPath = path.join(workspaceDir, '.wild', 'installed.json');
17
- this.watcher = null;
18
- }
19
-
20
- start() {
21
- this.watcher = chokidar.watch(
22
- [this.inboxPath, this.installedPath, path.join(this.workspaceDir, '.wild', 'imports')],
23
- {
24
- ignoreInitial: false,
25
- persistent: true,
26
- depth: 3,
27
- awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 100 },
28
- },
29
- );
30
- this.watcher.on('add', () => this._refresh('add'));
31
- this.watcher.on('change', () => this._refresh('change'));
32
- this.watcher.on('unlink', () => this._refresh('unlink'));
33
- return this;
34
- }
35
-
36
- async _refresh(kind) {
37
- const snapshot = await this.snapshot();
38
- this.emit('change', { kind, snapshot });
39
- }
40
-
41
- async snapshot() {
42
- const out = {
43
- hasInbox: existsSync(this.inboxPath),
44
- inboxContent: '',
45
- installed: {},
46
- imports: [],
47
- };
48
- if (out.hasInbox) {
49
- try {
50
- out.inboxContent = await fs.readFile(this.inboxPath, 'utf8');
51
- } catch {
52
- out.inboxContent = '';
53
- }
54
- }
55
- if (existsSync(this.installedPath)) {
56
- try {
57
- const raw = await fs.readFile(this.installedPath, 'utf8');
58
- out.installed = JSON.parse(raw);
59
- } catch {
60
- out.installed = {};
61
- }
62
- }
63
- const importsDir = path.join(this.workspaceDir, '.wild', 'imports');
64
- if (existsSync(importsDir)) {
65
- try {
66
- const entries = await fs.readdir(importsDir, { withFileTypes: true });
67
- out.imports = entries
68
- .filter((e) => e.isDirectory())
69
- .map((e) => e.name);
70
- } catch {
71
- out.imports = [];
72
- }
73
- }
74
- return out;
75
- }
76
-
77
- stop() {
78
- if (this.watcher) this.watcher.close();
79
- this.watcher = null;
80
- }
81
- }
1
+ // Component System inbox surfacing.
2
+ // When `.wild/inbox.md` exists or changes, emit notifications.
3
+ // Drives the "I noticed you imported X — want me to walk integration?" cue in chat.
4
+
5
+ import fs from 'node:fs/promises';
6
+ import { existsSync } from 'node:fs';
7
+ import path from 'node:path';
8
+ import chokidar from 'chokidar';
9
+ import { EventEmitter } from 'node:events';
10
+
11
+ export class InboxWatcher extends EventEmitter {
12
+ constructor(workspaceDir) {
13
+ super();
14
+ this.workspaceDir = workspaceDir;
15
+ this.inboxPath = path.join(workspaceDir, '.wild', 'inbox.md');
16
+ this.installedPath = path.join(workspaceDir, '.wild', 'installed.json');
17
+ this.watcher = null;
18
+ // Resolves once the watcher's initial scan is done — see start().
19
+ this.ready = Promise.resolve();
20
+ }
21
+
22
+ start() {
23
+ this.watcher = chokidar.watch(
24
+ [this.inboxPath, this.installedPath, path.join(this.workspaceDir, '.wild', 'imports')],
25
+ {
26
+ ignoreInitial: false,
27
+ persistent: true,
28
+ depth: 3,
29
+ awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 100 },
30
+ },
31
+ );
32
+ this.watcher.on('add', () => this._refresh('add'));
33
+ this.watcher.on('change', () => this._refresh('change'));
34
+ this.watcher.on('unlink', () => this._refresh('unlink'));
35
+ // Resolves once chokidar's initial scan completes and it is actively
36
+ // watching — anything created after this point is reliably detected.
37
+ this.ready = new Promise((resolve) => this.watcher.once('ready', resolve));
38
+ return this;
39
+ }
40
+
41
+ async _refresh(kind) {
42
+ const snapshot = await this.snapshot();
43
+ this.emit('change', { kind, snapshot });
44
+ }
45
+
46
+ async snapshot() {
47
+ const out = {
48
+ hasInbox: existsSync(this.inboxPath),
49
+ inboxContent: '',
50
+ installed: {},
51
+ imports: [],
52
+ };
53
+ if (out.hasInbox) {
54
+ try {
55
+ out.inboxContent = await fs.readFile(this.inboxPath, 'utf8');
56
+ } catch {
57
+ out.inboxContent = '';
58
+ }
59
+ }
60
+ if (existsSync(this.installedPath)) {
61
+ try {
62
+ const raw = await fs.readFile(this.installedPath, 'utf8');
63
+ out.installed = JSON.parse(raw);
64
+ } catch {
65
+ out.installed = {};
66
+ }
67
+ }
68
+ const importsDir = path.join(this.workspaceDir, '.wild', 'imports');
69
+ if (existsSync(importsDir)) {
70
+ try {
71
+ const entries = await fs.readdir(importsDir, { withFileTypes: true });
72
+ out.imports = entries
73
+ .filter((e) => e.isDirectory())
74
+ .map((e) => e.name);
75
+ } catch {
76
+ out.imports = [];
77
+ }
78
+ }
79
+ return out;
80
+ }
81
+
82
+ stop() {
83
+ if (this.watcher) this.watcher.close();
84
+ this.watcher = null;
85
+ }
86
+ }