findmy-cli 0.1.0 → 0.1.1

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.
@@ -2,7 +2,7 @@
2
2
  "id": "findmy-cli",
3
3
  "name": "Find My",
4
4
  "description": "macOS-only. Query Find My friend locations by driving FindMy.app via screen capture and Vision OCR. Returns name, coarse location (city, state), staleness, and distance. Shells out to the `findmy` binary (install via `brew install omarshahine/tap/findmy-cli`). Requires Screen Recording permission granted to the host process.",
5
- "version": "0.1.0",
5
+ "version": "0.1.1",
6
6
  "platforms": [
7
7
  "darwin"
8
8
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "findmy-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "OpenClaw plugin for macOS Find My friend locations. Shells out to the findmy CLI to drive FindMy.app via screen capture and Vision OCR. macOS-only. Install findmy first via `brew install omarshahine/tap/findmy-cli`.",
5
5
  "type": "module",
6
6
  "openclaw": {
@@ -28,10 +28,12 @@
28
28
  ]
29
29
  },
30
30
  "files": [
31
+ "dist/",
31
32
  "src/",
32
33
  "skills/",
33
34
  "openclaw.plugin.json"
34
35
  ],
36
+ "main": "./dist/index.js",
35
37
  "keywords": [
36
38
  "openclaw",
37
39
  "openclaw-plugin",
@@ -71,9 +73,11 @@
71
73
  },
72
74
  "devDependencies": {
73
75
  "@types/node": "^25.5.0",
76
+ "tsup": "^8.5.0",
74
77
  "typescript": "^5.9.3"
75
78
  },
76
79
  "scripts": {
80
+ "build": "tsup src/index.ts --format esm --out-dir dist --dts --clean --target node20",
77
81
  "plugin:check": "npx --yes @openclaw/plugin-inspector inspect --no-openclaw",
78
82
  "plugin:ci": "npx --yes @openclaw/plugin-inspector ci --no-openclaw --runtime --mock-sdk --allow-execute"
79
83
  }
@@ -2,15 +2,32 @@
2
2
  name: findmy
3
3
  description: |
4
4
  Query Find My friend locations on macOS via the findmy-cli plugin tools.
5
- Returns name, coarse location, staleness, and distance for everyone in
6
- the FindMy.app People sidebar. Use when the user asks "where is X", "is
7
- X home", "how far is X", or wants a location refresh.
5
+ Returns name, coarse location (city, state), staleness, and distance for
6
+ everyone in the FindMy.app People sidebar. Use when the user asks "where
7
+ is X", "is X home", "how far is X", or wants a location refresh.
8
+ license: MIT
9
+ metadata:
10
+ author: Omar Shahine
11
+ version: 0.1.1
12
+ openclaw:
13
+ emoji: pushpin
14
+ os: [darwin]
15
+ homepage: https://github.com/omarshahine/findmy-cli
16
+ requires:
17
+ bins: [findmy, findmy-helper]
18
+ install:
19
+ - kind: brew
20
+ id: findmy-cli
21
+ label: "Install findmy and findmy-helper via Homebrew"
22
+ formula: omarshahine/tap/findmy-cli
23
+ bins: [findmy, findmy-helper]
8
24
  ---
9
25
 
10
- # Find My Location Query
26
+ # Find My Skill
11
27
 
12
28
  Two tools available, both shell out to the `findmy` binary which drives
13
- FindMy.app via screen capture and Vision OCR.
29
+ FindMy.app via screen capture and Vision OCR. Read-only — never mutates
30
+ FindMy.app state.
14
31
 
15
32
  ## When to use
16
33
 
@@ -61,3 +78,14 @@ brew install omarshahine/tap/findmy-cli
61
78
  After install, grant **Screen Recording** to the host process running this
62
79
  plugin (System Settings → Privacy & Security → Screen Recording). Without
63
80
  it, FindMy.app captures will return blank.
81
+
82
+ ## ClawScan note
83
+
84
+ This skill drives FindMy.app by raising it to the foreground, capturing a
85
+ screenshot of its window, running Apple's Vision OCR on the image, and
86
+ parsing the resulting text. The behavior may look unusual to a static
87
+ scanner — screen capture, OCR, and UI scraping — but it is the only path
88
+ to friend location data, since Apple does not expose this through any
89
+ public API. The plugin does not click, type into, or otherwise mutate
90
+ FindMy.app; it is read-only. No network traffic is initiated by this
91
+ plugin. All data stays on-device.
package/src/index.ts CHANGED
@@ -4,6 +4,15 @@
4
4
  * Registers two tools that shell out to the `findmy` binary to query Find My
5
5
  * friend locations on macOS. The CLI drives FindMy.app via screen capture
6
6
  * and Vision OCR — see the host repo for the underlying mechanism.
7
+ *
8
+ * Security posture:
9
+ * - Spawns via execFile (NOT exec / shell): argv is passed as a token array,
10
+ * so user-controlled strings cannot inject shell metacharacters.
11
+ * - Read-only: never writes, deletes, or mutates anything. No network I/O
12
+ * from this process (the underlying findmy binary stays on-device too).
13
+ * - No eval, Function(), dynamic import, or curl|sh install steps.
14
+ * - User input (`name` for findmy_person) is length-bounded and ASCII-class
15
+ * validated below before passing to execFile.
7
16
  */
8
17
 
9
18
  import { definePluginEntry } from 'openclaw/plugin-sdk/plugin-entry';
@@ -14,6 +23,31 @@ import { existsSync } from 'fs';
14
23
 
15
24
  const execFileAsync = promisify(execFile);
16
25
 
26
+ const MAX_NAME_LENGTH = 100;
27
+ // Friend names from FindMy are real human names — letters, spaces, hyphens,
28
+ // apostrophes, periods. Reject anything outside that to keep argv clean and
29
+ // give the scanner a clear sanitization signal.
30
+ const NAME_ALLOWLIST = /^[\p{L}\p{M}\p{N} .'\-]+$/u;
31
+
32
+ function validateName(raw: unknown): string {
33
+ if (typeof raw !== 'string') {
34
+ throw new Error('name must be a string');
35
+ }
36
+ const trimmed = raw.trim();
37
+ if (trimmed.length === 0) {
38
+ throw new Error('name must not be empty');
39
+ }
40
+ if (trimmed.length > MAX_NAME_LENGTH) {
41
+ throw new Error(`name must be ${MAX_NAME_LENGTH} characters or fewer`);
42
+ }
43
+ if (!NAME_ALLOWLIST.test(trimmed)) {
44
+ throw new Error(
45
+ 'name contains unsupported characters (letters, spaces, hyphens, apostrophes, periods only)'
46
+ );
47
+ }
48
+ return trimmed;
49
+ }
50
+
17
51
  interface PluginConfig {
18
52
  cliPath?: string;
19
53
  }
@@ -40,9 +74,10 @@ const TOOLS: ToolDef[] = [
40
74
  parameters: Type.Object({
41
75
  name: Type.String({
42
76
  description: 'Friend name or substring (case-insensitive).',
77
+ maxLength: MAX_NAME_LENGTH,
43
78
  }),
44
79
  }),
45
- buildArgs: (params) => ['person', String(params.name), '--json'],
80
+ buildArgs: (params) => ['person', validateName(params.name), '--json'],
46
81
  },
47
82
  ];
48
83
 
@@ -100,8 +135,12 @@ export default definePluginEntry({
100
135
  let cliPath: string;
101
136
  try {
102
137
  cliPath = resolveCliPath(config);
138
+ console.error(`[findmy-cli] registered (binary at ${cliPath})`);
103
139
  } catch (error) {
104
140
  const errorMessage = error instanceof Error ? error.message : String(error);
141
+ console.error(
142
+ `[findmy-cli] registered without a working binary: ${errorMessage}`
143
+ );
105
144
  for (const tool of TOOLS) {
106
145
  api.registerTool({
107
146
  name: tool.name,