findmy-cli 0.1.1 → 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.
- package/dist/index.d.ts +27 -0
- package/dist/index.js +150 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/skills/findmy/SKILL.md +1 -1
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as openclaw_plugin_sdk_plugin_entry from 'openclaw/plugin-sdk/plugin-entry';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* OpenClaw plugin entry for findmy-cli.
|
|
5
|
+
*
|
|
6
|
+
* Registers two tools that shell out to the `findmy` binary to query Find My
|
|
7
|
+
* friend locations on macOS. The CLI drives FindMy.app via screen capture
|
|
8
|
+
* and Vision OCR — see the host repo for the underlying mechanism.
|
|
9
|
+
*
|
|
10
|
+
* Security posture:
|
|
11
|
+
* - Spawns via execFile (NOT exec / shell): argv is passed as a token array,
|
|
12
|
+
* so user-controlled strings cannot inject shell metacharacters.
|
|
13
|
+
* - Read-only: never writes, deletes, or mutates anything. No network I/O
|
|
14
|
+
* from this process (the underlying findmy binary stays on-device too).
|
|
15
|
+
* - No eval, Function(), dynamic import, or curl|sh install steps.
|
|
16
|
+
* - User input (`name` for findmy_person) is length-bounded and ASCII-class
|
|
17
|
+
* validated below before passing to execFile.
|
|
18
|
+
*/
|
|
19
|
+
declare const _default: {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
description: string;
|
|
23
|
+
configSchema: openclaw_plugin_sdk_plugin_entry.OpenClawPluginConfigSchema;
|
|
24
|
+
register: NonNullable<openclaw_plugin_sdk_plugin_entry.OpenClawPluginDefinition["register"]>;
|
|
25
|
+
} & Pick<openclaw_plugin_sdk_plugin_entry.OpenClawPluginDefinition, "kind" | "reload" | "nodeHostCommands" | "securityAuditCollectors">;
|
|
26
|
+
|
|
27
|
+
export { _default as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
3
|
+
import { Type } from "@sinclair/typebox";
|
|
4
|
+
import { execFileSync, execFile } from "child_process";
|
|
5
|
+
import { promisify } from "util";
|
|
6
|
+
import { existsSync } from "fs";
|
|
7
|
+
var execFileAsync = promisify(execFile);
|
|
8
|
+
var MAX_NAME_LENGTH = 100;
|
|
9
|
+
var NAME_ALLOWLIST = /^[\p{L}\p{M}\p{N} .'\-]+$/u;
|
|
10
|
+
function validateName(raw) {
|
|
11
|
+
if (typeof raw !== "string") {
|
|
12
|
+
throw new Error("name must be a string");
|
|
13
|
+
}
|
|
14
|
+
const trimmed = raw.trim();
|
|
15
|
+
if (trimmed.length === 0) {
|
|
16
|
+
throw new Error("name must not be empty");
|
|
17
|
+
}
|
|
18
|
+
if (trimmed.length > MAX_NAME_LENGTH) {
|
|
19
|
+
throw new Error(`name must be ${MAX_NAME_LENGTH} characters or fewer`);
|
|
20
|
+
}
|
|
21
|
+
if (!NAME_ALLOWLIST.test(trimmed)) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
"name contains unsupported characters (letters, spaces, hyphens, apostrophes, periods only)"
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return trimmed;
|
|
27
|
+
}
|
|
28
|
+
var TOOLS = [
|
|
29
|
+
{
|
|
30
|
+
name: "findmy_people",
|
|
31
|
+
description: 'List every friend in the FindMy.app People sidebar. Returns name, coarse location (city, state), staleness, and distance for each person. Use for "who is where", "anyone near downtown", or any broad location query. Each entry includes a `staleness` field \u2014 if "Paused", the friend has paused location sharing and the location is the last known position.',
|
|
32
|
+
parameters: Type.Object({}),
|
|
33
|
+
buildArgs: () => ["people", "--json"]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "findmy_person",
|
|
37
|
+
description: 'Look up a single friend by name (case-insensitive substring match). Returns the same shape as findmy_people but filtered. Use for "where is X", "is X home", "how far is X". Match works on partial names \u2014 "sarah" matches "Sarah Shahine".',
|
|
38
|
+
parameters: Type.Object({
|
|
39
|
+
name: Type.String({
|
|
40
|
+
description: "Friend name or substring (case-insensitive).",
|
|
41
|
+
maxLength: MAX_NAME_LENGTH
|
|
42
|
+
})
|
|
43
|
+
}),
|
|
44
|
+
buildArgs: (params) => ["person", validateName(params.name), "--json"]
|
|
45
|
+
}
|
|
46
|
+
];
|
|
47
|
+
function toolResult(text) {
|
|
48
|
+
return {
|
|
49
|
+
content: [{ type: "text", text }],
|
|
50
|
+
details: void 0
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function whichBinary(name) {
|
|
54
|
+
const cmd = process.platform === "win32" ? "where.exe" : "which";
|
|
55
|
+
try {
|
|
56
|
+
const result = execFileSync(cmd, [name], { encoding: "utf8" }).trim();
|
|
57
|
+
const first = result.split("\n")[0]?.trim();
|
|
58
|
+
return first || null;
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function resolveCliPath(config) {
|
|
64
|
+
if (config?.cliPath && existsSync(config.cliPath)) {
|
|
65
|
+
return config.cliPath;
|
|
66
|
+
}
|
|
67
|
+
const envPath = process.env.FINDMY_CLI_PATH;
|
|
68
|
+
if (envPath && existsSync(envPath)) {
|
|
69
|
+
return envPath;
|
|
70
|
+
}
|
|
71
|
+
const found = whichBinary("findmy");
|
|
72
|
+
if (found) return found;
|
|
73
|
+
throw new Error(
|
|
74
|
+
"findmy not found on PATH. Install with: brew install omarshahine/tap/findmy-cli\nOr set FINDMY_CLI_PATH or configure cliPath in plugin settings."
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
var index_default = definePluginEntry({
|
|
78
|
+
id: "findmy-cli",
|
|
79
|
+
name: "Find My",
|
|
80
|
+
description: "Query Find My friend locations on macOS via UI scraping",
|
|
81
|
+
register(api) {
|
|
82
|
+
const config = api.pluginConfig;
|
|
83
|
+
let cliPath;
|
|
84
|
+
try {
|
|
85
|
+
cliPath = resolveCliPath(config);
|
|
86
|
+
console.error(`[findmy-cli] registered (binary at ${cliPath})`);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
89
|
+
console.error(
|
|
90
|
+
`[findmy-cli] registered without a working binary: ${errorMessage}`
|
|
91
|
+
);
|
|
92
|
+
for (const tool of TOOLS) {
|
|
93
|
+
api.registerTool({
|
|
94
|
+
name: tool.name,
|
|
95
|
+
label: tool.name,
|
|
96
|
+
description: tool.description,
|
|
97
|
+
parameters: tool.parameters,
|
|
98
|
+
async execute() {
|
|
99
|
+
return toolResult(
|
|
100
|
+
JSON.stringify({ success: false, error: errorMessage }, null, 2)
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
for (const tool of TOOLS) {
|
|
108
|
+
api.registerTool({
|
|
109
|
+
name: tool.name,
|
|
110
|
+
label: tool.name,
|
|
111
|
+
description: tool.description,
|
|
112
|
+
parameters: tool.parameters,
|
|
113
|
+
async execute(_id, params) {
|
|
114
|
+
try {
|
|
115
|
+
const args = tool.buildArgs(params);
|
|
116
|
+
const { stdout } = await execFileAsync(cliPath, args, {
|
|
117
|
+
encoding: "utf8",
|
|
118
|
+
// FindMy.app capture is slow on cold boot — it has to launch,
|
|
119
|
+
// switch tabs, render the sidebar, and OCR a screenshot.
|
|
120
|
+
timeout: 6e4,
|
|
121
|
+
maxBuffer: 4 * 1024 * 1024
|
|
122
|
+
});
|
|
123
|
+
if (stdout.trim().length === 0) {
|
|
124
|
+
return toolResult(JSON.stringify({ success: true }, null, 2));
|
|
125
|
+
}
|
|
126
|
+
let result;
|
|
127
|
+
try {
|
|
128
|
+
result = JSON.parse(stdout);
|
|
129
|
+
} catch {
|
|
130
|
+
result = { output: stdout.trim() };
|
|
131
|
+
}
|
|
132
|
+
return toolResult(JSON.stringify(result, null, 2));
|
|
133
|
+
} catch (error) {
|
|
134
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
135
|
+
const stderr = error && typeof error === "object" && "stderr" in error ? String(error.stderr).trim() : "";
|
|
136
|
+
const errorOutput = stderr ? `${message}
|
|
137
|
+
|
|
138
|
+
stderr: ${stderr}` : message;
|
|
139
|
+
return toolResult(
|
|
140
|
+
JSON.stringify({ success: false, error: errorOutput }, null, 2)
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
export {
|
|
149
|
+
index_default as default
|
|
150
|
+
};
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
5
|
+
"version": "0.1.2",
|
|
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.
|
|
3
|
+
"version": "0.1.2",
|
|
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": {
|