@farmslot/capabilities 0.1.0 → 0.1.3

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/CHANGELOG.md CHANGED
@@ -4,6 +4,23 @@ All notable changes to `@farmslot/capabilities` are tracked here.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ - Active-development baseline; add user-facing changes here before release or package publication.
8
+
9
+ ## 0.1.3 - 2026-10-09
10
+
11
+ - Publish with protocol 0.35.0 so consumers share one protocol copy. No code change.
12
+
13
+ ## 0.1.2 - 2026-09-20
14
+
15
+ - Publishable manifest: MIT license file and metadata, an explicit `files` list (source only, no tests) and public access, so the package ships through the shared npm release group.
16
+
17
+ ## 0.1.1 - 2026-08-21
18
+
19
+ - Keep `fs-watch` attached across atomic file replacements so remote checklist progress
20
+ continues after the first marked step.
21
+
22
+ ## 0.1.0 - 2026-07-01
23
+
7
24
  - Initial package (ADR-046): machine-local capability primitives shared by the node
8
25
  (primary owner) and the gateway (local fallback when no node).
9
26
  - `fs-watch` — `watchFile()` native file-change watch primitive.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Arthur Breton
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -3,6 +3,8 @@
3
3
  Machine-local capability primitives shared by the **node** (`services/node`) and the
4
4
  **gateway** (`services/gateway`).
5
5
 
6
+ Reference: <https://farmslot.io/docs/reference/capabilities>.
7
+
6
8
  The node owns machine-local capabilities; the gateway keeps a degraded **local fallback**
7
9
  for a machine that has no connected node. To provide that fallback without duplicating
8
10
  logic, the primitive lives here once and both services import it — node as primary owner,
@@ -26,11 +28,11 @@ import { encodeNodeFrame, decodeNodeFrame } from '@farmslot/capabilities/screen-
26
28
  import { createH264FrameSplitter } from '@farmslot/capabilities/screen-h264';
27
29
  ```
28
30
 
29
- | Import | What it is |
30
- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
31
- | `fs-watch` — `watchFile()` | Native `fs.watch` with the "watch the parent directory until the file appears" fallback and tilde expansion. Returns a stop handle. |
32
- | `screen-frame` — `encodeNodeFrame()` / `decodeNodeFrame()` | The node→gateway capture-frame binary envelope codec (`0xAF` magic). Node encodes, gateway decodes. |
33
- | `screen-h264` — `createH264FrameSplitter()` | Splits a raw `adb screenrecord --output-format=h264` byte stream into individual video frames. |
31
+ | Import | What it is |
32
+ | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
33
+ | `fs-watch` — `watchFile()` | Native parent-directory `fs.watch` that survives atomic file replacement, filters to the target filename, deduplicates content, and expands tildes. Returns a stop handle. |
34
+ | `screen-frame` — `encodeNodeFrame()` / `decodeNodeFrame()` | The node→gateway capture-frame binary envelope codec (`0xAF` magic). Node encodes, gateway decodes. |
35
+ | `screen-h264` — `createH264FrameSplitter()` | Splits a raw `adb screenrecord --output-format=h264` byte stream into individual video frames. |
34
36
 
35
37
  ## Source layout
36
38
 
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@farmslot/capabilities",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "description": "Machine-local capability primitives (fs-watch, screen capture) shared by the node (primary owner) and the gateway (local fallback when no node) — ADR-046.",
5
+ "license": "MIT",
5
6
  "type": "module",
6
7
  "main": "src/index.ts",
7
8
  "exports": {
@@ -10,15 +11,35 @@
10
11
  "./screen-frame": "./src/screen-frame.ts",
11
12
  "./screen-h264": "./src/screen-h264.ts"
12
13
  },
14
+ "files": [
15
+ "README.md",
16
+ "LICENSE",
17
+ "src/**/*.ts",
18
+ "!src/**/*.test.ts"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
13
23
  "scripts": {
24
+ "test": "node ../../scripts/quality/run-tsx-tests.mjs --cwd . --tsconfig tsconfig.json src",
14
25
  "typecheck": "tsc --noEmit --project tsconfig.json",
15
- "quality": "yarn typecheck"
26
+ "quality": "yarn test && yarn typecheck"
16
27
  },
17
28
  "dependencies": {
18
- "@farmslot/protocol": "0.7.2"
29
+ "@farmslot/protocol": "0.35.0"
19
30
  },
20
31
  "devDependencies": {
21
32
  "@types/node": "^22.0.0",
33
+ "tsx": "^4.19.0",
22
34
  "typescript": "^5.6.0"
23
- }
35
+ },
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/deeeed/farmslot.git",
39
+ "directory": "packages/capabilities"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/deeeed/farmslot/issues"
43
+ },
44
+ "homepage": "https://farmslot.io/docs/reference/capabilities"
24
45
  }
package/src/fs-watch.ts CHANGED
@@ -1,5 +1,5 @@
1
- // Shared file-watch primitive (ADR-046). Native `fs.watch` with the parent-dir
2
- // "watch until the file appears" fallback and tilde expansion. Used by the node
1
+ // Shared file-watch primitive (ADR-046). Native parent-directory `fs.watch`
2
+ // survives atomic file replacement and expands tilde paths. Used by the node
3
3
  // (primary owner of machine-local monitoring) and by the gateway as a local
4
4
  // fallback when a machine has no connected node — one implementation, two callers.
5
5
 
@@ -22,62 +22,44 @@ export interface FileWatchHandle {
22
22
 
23
23
  /**
24
24
  * Watch a single file for content changes. `onChange` receives the file's UTF-8
25
- * content on every write. If the file does not exist yet, its parent directory is
26
- * watched until the file appears, then the watch switches to the file directly.
27
- * Returns a handle the caller stops when done.
25
+ * content on every write. The parent directory remains watched so atomic file
26
+ * replacement does not detach the watcher from subsequent updates. Returns a
27
+ * handle the caller stops when done.
28
28
  */
29
29
  export function watchFile(filePath: string, onChange: (content: string) => void): FileWatchHandle {
30
30
  const targetPath = expandTilde(filePath);
31
31
  let watcher: FSWatcher | null = null;
32
32
  let stopped = false;
33
+ let lastContent: string | null = null;
33
34
 
34
35
  const start = (): void => {
35
36
  if (stopped) return;
36
- if (existsSync(targetPath)) {
37
- // File exists — watch it directly.
38
- watcher = watch(targetPath, { persistent: false }, async () => {
39
- try {
40
- const content = await readFile(targetPath, 'utf-8');
41
- onChange(content);
42
- } catch (err) {
43
- // ENOENT is expected: git rewrites HEAD via an atomic rename, so the path is
44
- // briefly absent between the watch firing and the read — the next event re-reads.
45
- // Anything else (permissions, I/O) is a real fault and is surfaced, not hidden.
46
- const code = (err as NodeJS.ErrnoException).code;
47
- if (code !== 'ENOENT') {
48
- console.warn(`[fs.watch] read failed for ${targetPath}: ${(err as Error).message}`);
49
- }
37
+ // Watch the parent directory even when the file already exists. Checklist markers,
38
+ // git, and many editors update files through atomic rename; watching the file inode
39
+ // sees the first replacement and then silently follows the orphaned inode.
40
+ const dir = dirname(targetPath);
41
+ if (!existsSync(dir)) {
42
+ console.log(`[fs.watch] parent dir doesn't exist: ${dir} — skipping watch`);
43
+ return;
44
+ }
45
+ const target = basename(targetPath);
46
+ watcher = watch(dir, { persistent: false }, async (_, filename) => {
47
+ if (filename != null && filename.toString() !== target) return;
48
+ if (!existsSync(targetPath)) return;
49
+ try {
50
+ const content = await readFile(targetPath, 'utf-8');
51
+ if (content === lastContent) return;
52
+ lastContent = content;
53
+ onChange(content);
54
+ } catch (err) {
55
+ // Atomic replacement can briefly remove the target between the directory
56
+ // event and the read. The following event will read the replacement.
57
+ const code = (err as NodeJS.ErrnoException).code;
58
+ if (code !== 'ENOENT') {
59
+ console.warn(`[fs.watch] read failed for ${targetPath}: ${(err as Error).message}`);
50
60
  }
51
- });
52
- } else {
53
- // File doesn't exist yet — watch the parent directory for its creation.
54
- const dir = dirname(targetPath);
55
- if (!existsSync(dir)) {
56
- console.log(`[fs.watch] parent dir doesn't exist: ${dir} — skipping watch`);
57
- return;
58
61
  }
59
- const target = basename(targetPath);
60
- watcher = watch(dir, { persistent: false }, async (_, filename) => {
61
- if (filename !== target) return;
62
- if (!existsSync(targetPath)) return;
63
- // File appeared — read, notify, then switch to watching it directly.
64
- try {
65
- const content = await readFile(targetPath, 'utf-8');
66
- onChange(content);
67
- } catch (err) {
68
- // ENOENT is expected: git rewrites HEAD via an atomic rename, so the path is
69
- // briefly absent between the watch firing and the read — the next event re-reads.
70
- // Anything else (permissions, I/O) is a real fault and is surfaced, not hidden.
71
- const code = (err as NodeJS.ErrnoException).code;
72
- if (code !== 'ENOENT') {
73
- console.warn(`[fs.watch] read failed for ${targetPath}: ${(err as Error).message}`);
74
- }
75
- }
76
- if (watcher) watcher.close();
77
- watcher = null;
78
- start();
79
- });
80
- }
62
+ });
81
63
  };
82
64
 
83
65
  start();
package/tsconfig.json DELETED
@@ -1,12 +0,0 @@
1
- {
2
- "extends": "../../apps/command-center/tsconfig.base.json",
3
- "compilerOptions": {
4
- "outDir": "dist",
5
- "paths": {
6
- "@farmslot/protocol": ["../protocol/src/index.ts"],
7
- "@farmslot/protocol/*": ["../protocol/src/*"]
8
- }
9
- },
10
- "include": ["src/**/*.ts"],
11
- "exclude": ["src/**/*.test.ts"]
12
- }