@oceanswave/openclaw-tescmd 0.3.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.
- package/LICENSE +21 -0
- package/README.md +183 -0
- package/commands/cli.ts +74 -0
- package/commands/slash.ts +160 -0
- package/config.ts +39 -0
- package/index.ts +107 -0
- package/openclaw.plugin.json +22 -0
- package/package.json +46 -0
- package/platform.ts +361 -0
- package/skill.md +737 -0
- package/telemetry.ts +245 -0
- package/tools/capabilities.ts +341 -0
- package/tools/charge.ts +141 -0
- package/tools/climate.ts +134 -0
- package/tools/navigation.ts +199 -0
- package/tools/security.ts +207 -0
- package/tools/status.ts +47 -0
- package/tools/system.ts +67 -0
- package/tools/triggers.ts +311 -0
- package/tools/trunk.ts +67 -0
- package/tools/vehicle.ts +115 -0
- package/types/openclaw.d.ts +108 -0
package/tools/status.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node status tool — check if a tescmd node is connected.
|
|
3
|
+
*
|
|
4
|
+
* Lets the agent detect whether a tescmd node is currently online
|
|
5
|
+
* and connected to the Gateway before attempting to invoke commands.
|
|
6
|
+
* If no node is connected, the agent can suggest spawning one via
|
|
7
|
+
* `tescmd serve <VIN> --openclaw <url>`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Type } from "@sinclair/typebox";
|
|
11
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
12
|
+
|
|
13
|
+
export function registerStatusTool(api: OpenClawPluginApi): void {
|
|
14
|
+
api.registerTool(
|
|
15
|
+
{
|
|
16
|
+
name: "tescmd_node_status",
|
|
17
|
+
label: "Check Node Status",
|
|
18
|
+
description:
|
|
19
|
+
"Check whether a tescmd node is currently connected to the OpenClaw Gateway. " +
|
|
20
|
+
"Returns the node's connection status, capabilities, and last-seen timestamp. " +
|
|
21
|
+
"\n\nWhen to use: ALWAYS call this before invoking any vehicle command if you " +
|
|
22
|
+
"haven't confirmed the node is online in this conversation. If the node is " +
|
|
23
|
+
"offline, suggest the user start one with: " +
|
|
24
|
+
"`tescmd serve <VIN> --openclaw <gateway_url> --openclaw-token <token>`. " +
|
|
25
|
+
"Also useful for diagnostic checks if commands are failing. " +
|
|
26
|
+
"\n\nReturns: {connected: boolean, node_id?: string, platform: 'tesla', " +
|
|
27
|
+
"commands_available?: number, last_seen?: string}. " +
|
|
28
|
+
"If connected is false, all other vehicle tools will fail.",
|
|
29
|
+
parameters: Type.Object({}),
|
|
30
|
+
async execute(_toolCallId: string, _params: Record<string, unknown>) {
|
|
31
|
+
return {
|
|
32
|
+
content: [
|
|
33
|
+
{
|
|
34
|
+
type: "text" as const,
|
|
35
|
+
text: "Checking tescmd node connection status...",
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
details: {
|
|
39
|
+
gatewayQuery: "node.status",
|
|
40
|
+
platform: "tesla",
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
{ name: "tescmd_node_status" },
|
|
46
|
+
);
|
|
47
|
+
}
|
package/tools/system.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System meta-dispatch tool.
|
|
3
|
+
*
|
|
4
|
+
* The `system.run` command is a meta-dispatch that can invoke any
|
|
5
|
+
* registered handler by name. It accepts both OpenClaw dot-notation
|
|
6
|
+
* (e.g. "door.lock") and Tesla Fleet API snake_case names
|
|
7
|
+
* (e.g. "door_lock") via an internal alias table.
|
|
8
|
+
*
|
|
9
|
+
* This is the escape hatch for commands that don't have dedicated tools,
|
|
10
|
+
* or for programmatic dispatch when the method name is determined at runtime.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { Type } from "@sinclair/typebox";
|
|
14
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
15
|
+
|
|
16
|
+
export function registerSystemTools(api: OpenClawPluginApi): void {
|
|
17
|
+
api.registerTool(
|
|
18
|
+
{
|
|
19
|
+
name: "tescmd_run_command",
|
|
20
|
+
label: "Run Tesla Command",
|
|
21
|
+
description:
|
|
22
|
+
"Meta-dispatch: execute any registered Tesla vehicle command by name. " +
|
|
23
|
+
"Accepts both OpenClaw dot-notation (e.g. 'door.lock', 'climate.on') " +
|
|
24
|
+
"and Tesla Fleet API snake_case method names (e.g. 'door_lock', " +
|
|
25
|
+
"'auto_conditioning_start'). Cannot invoke itself (system.run). " +
|
|
26
|
+
"\n\nKnown method aliases:\n" +
|
|
27
|
+
" door_lock → door.lock\n" +
|
|
28
|
+
" door_unlock → door.unlock\n" +
|
|
29
|
+
" auto_conditioning_start → climate.on\n" +
|
|
30
|
+
" auto_conditioning_stop → climate.off\n" +
|
|
31
|
+
" set_temps → climate.set_temp\n" +
|
|
32
|
+
" charge_start → charge.start\n" +
|
|
33
|
+
" charge_stop → charge.stop\n" +
|
|
34
|
+
" set_charge_limit → charge.set_limit\n" +
|
|
35
|
+
" actuate_trunk → trunk.open\n" +
|
|
36
|
+
"\nUse this tool when you need to run a command that doesn't have " +
|
|
37
|
+
"a dedicated tool, or when the command name is determined dynamically.",
|
|
38
|
+
parameters: Type.Object({
|
|
39
|
+
method: Type.String({
|
|
40
|
+
description: "Command method name — dot-notation (door.lock) or snake_case (door_lock)",
|
|
41
|
+
}),
|
|
42
|
+
params: Type.Optional(
|
|
43
|
+
Type.Record(Type.String(), Type.Unknown(), {
|
|
44
|
+
description: "Optional parameters to pass to the command",
|
|
45
|
+
}),
|
|
46
|
+
),
|
|
47
|
+
}),
|
|
48
|
+
async execute(_toolCallId: string, params: Record<string, unknown>) {
|
|
49
|
+
const method = params.method as string;
|
|
50
|
+
const cmdParams = (params.params as Record<string, unknown>) ?? {};
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
type: "text" as const,
|
|
55
|
+
text: `Invoking system.run with method=${method} on tescmd node`,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
details: {
|
|
59
|
+
nodeMethod: "system.run",
|
|
60
|
+
params: { method, params: cmdParams },
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{ name: "tescmd_run_command" },
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trigger subscription tools — CRUD, polling, and convenience aliases.
|
|
3
|
+
*
|
|
4
|
+
* Triggers allow agents to subscribe to telemetry conditions and receive
|
|
5
|
+
* notifications when thresholds are crossed. For example, alert when
|
|
6
|
+
* battery drops below 20% or when the vehicle leaves a geofence.
|
|
7
|
+
*
|
|
8
|
+
* Supported trigger operators:
|
|
9
|
+
* lt, gt, lte, gte, eq, neq — numeric comparisons
|
|
10
|
+
* changed — fires on any value change
|
|
11
|
+
* enter, leave — geofence operators (for Location field)
|
|
12
|
+
*
|
|
13
|
+
* Supported telemetry fields for triggers:
|
|
14
|
+
* Soc, BatteryLevel, InsideTemp, OutsideTemp, VehicleSpeed,
|
|
15
|
+
* ChargeState, Locked, SentryMode, Location, Gear
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { Type } from "@sinclair/typebox";
|
|
19
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
20
|
+
import { stringEnum } from "openclaw/plugin-sdk";
|
|
21
|
+
|
|
22
|
+
const TRIGGER_OPERATORS = [
|
|
23
|
+
"lt",
|
|
24
|
+
"gt",
|
|
25
|
+
"lte",
|
|
26
|
+
"gte",
|
|
27
|
+
"eq",
|
|
28
|
+
"neq",
|
|
29
|
+
"changed",
|
|
30
|
+
"enter",
|
|
31
|
+
"leave",
|
|
32
|
+
] as const;
|
|
33
|
+
|
|
34
|
+
const TRIGGER_FIELDS = [
|
|
35
|
+
"Soc",
|
|
36
|
+
"BatteryLevel",
|
|
37
|
+
"InsideTemp",
|
|
38
|
+
"OutsideTemp",
|
|
39
|
+
"VehicleSpeed",
|
|
40
|
+
"ChargeState",
|
|
41
|
+
"DetailedChargeState",
|
|
42
|
+
"Locked",
|
|
43
|
+
"SentryMode",
|
|
44
|
+
"Location",
|
|
45
|
+
"Gear",
|
|
46
|
+
"EstBatteryRange",
|
|
47
|
+
] as const;
|
|
48
|
+
|
|
49
|
+
export function registerTriggerTools(api: OpenClawPluginApi): void {
|
|
50
|
+
// -----------------------------------------------------------------
|
|
51
|
+
// tescmd_list_triggers
|
|
52
|
+
// -----------------------------------------------------------------
|
|
53
|
+
api.registerTool(
|
|
54
|
+
{
|
|
55
|
+
name: "tescmd_list_triggers",
|
|
56
|
+
label: "List Triggers",
|
|
57
|
+
description:
|
|
58
|
+
"List all active trigger subscriptions on the tescmd node. Returns " +
|
|
59
|
+
"{triggers: [{id, field, operator, value, once, cooldown_seconds}]}. " +
|
|
60
|
+
"Each trigger monitors a telemetry field and fires when the condition " +
|
|
61
|
+
"is met. Use trigger IDs from this list with tescmd_delete_trigger.",
|
|
62
|
+
parameters: Type.Object({}),
|
|
63
|
+
async execute(_toolCallId: string, _params: Record<string, unknown>) {
|
|
64
|
+
return {
|
|
65
|
+
content: [{ type: "text" as const, text: "Invoking trigger.list on tescmd node" }],
|
|
66
|
+
details: { nodeMethod: "trigger.list", params: {} },
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{ name: "tescmd_list_triggers" },
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// -----------------------------------------------------------------
|
|
74
|
+
// tescmd_poll_triggers
|
|
75
|
+
// -----------------------------------------------------------------
|
|
76
|
+
api.registerTool(
|
|
77
|
+
{
|
|
78
|
+
name: "tescmd_poll_triggers",
|
|
79
|
+
label: "Poll Trigger Notifications",
|
|
80
|
+
description:
|
|
81
|
+
"Poll for trigger notifications that have fired since the last poll. " +
|
|
82
|
+
"Returns {notifications: []} where each notification contains the " +
|
|
83
|
+
"trigger_id, field name, current value, and fired_at timestamp. " +
|
|
84
|
+
"Drains the pending notification queue — calling again immediately " +
|
|
85
|
+
"will return empty unless new triggers have fired.",
|
|
86
|
+
parameters: Type.Object({}),
|
|
87
|
+
async execute(_toolCallId: string, _params: Record<string, unknown>) {
|
|
88
|
+
return {
|
|
89
|
+
content: [{ type: "text" as const, text: "Invoking trigger.poll on tescmd node" }],
|
|
90
|
+
details: { nodeMethod: "trigger.poll", params: {} },
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
{ name: "tescmd_poll_triggers" },
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// -----------------------------------------------------------------
|
|
98
|
+
// tescmd_create_trigger
|
|
99
|
+
// -----------------------------------------------------------------
|
|
100
|
+
api.registerTool(
|
|
101
|
+
{
|
|
102
|
+
name: "tescmd_create_trigger",
|
|
103
|
+
label: "Create Trigger",
|
|
104
|
+
description:
|
|
105
|
+
"Create a new telemetry trigger subscription. The trigger fires when " +
|
|
106
|
+
"the specified telemetry field meets the condition (operator + value). " +
|
|
107
|
+
"For example: field='Soc', operator='lt', value=20 fires when battery " +
|
|
108
|
+
"drops below 20%. For geofence: field='Location', operator='leave', " +
|
|
109
|
+
"with lat/lon/radius in value. Returns {id, field, operator} on success.",
|
|
110
|
+
parameters: Type.Object({
|
|
111
|
+
field: stringEnum(TRIGGER_FIELDS),
|
|
112
|
+
operator: stringEnum(TRIGGER_OPERATORS),
|
|
113
|
+
value: Type.Optional(
|
|
114
|
+
Type.Union([Type.Number(), Type.String(), Type.Boolean()], {
|
|
115
|
+
description: "Threshold value for the condition",
|
|
116
|
+
}),
|
|
117
|
+
),
|
|
118
|
+
once: Type.Optional(
|
|
119
|
+
Type.Boolean({
|
|
120
|
+
description: "If true, the trigger fires only once then auto-deletes (default: false)",
|
|
121
|
+
}),
|
|
122
|
+
),
|
|
123
|
+
cooldown_seconds: Type.Optional(
|
|
124
|
+
Type.Number({
|
|
125
|
+
description: "Minimum seconds between consecutive fires for this trigger (default: 60)",
|
|
126
|
+
minimum: 0,
|
|
127
|
+
}),
|
|
128
|
+
),
|
|
129
|
+
}),
|
|
130
|
+
async execute(_toolCallId: string, params: Record<string, unknown>) {
|
|
131
|
+
return {
|
|
132
|
+
content: [
|
|
133
|
+
{
|
|
134
|
+
type: "text" as const,
|
|
135
|
+
text: `Creating trigger: ${params.field} ${params.operator} ${params.value ?? "(any change)"}`,
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
details: { nodeMethod: "trigger.create", params },
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
{ name: "tescmd_create_trigger" },
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// -----------------------------------------------------------------
|
|
146
|
+
// tescmd_delete_trigger
|
|
147
|
+
// -----------------------------------------------------------------
|
|
148
|
+
api.registerTool(
|
|
149
|
+
{
|
|
150
|
+
name: "tescmd_delete_trigger",
|
|
151
|
+
label: "Delete Trigger",
|
|
152
|
+
description:
|
|
153
|
+
"Delete an existing trigger subscription by its ID. Get trigger IDs " +
|
|
154
|
+
"from tescmd_list_triggers. Returns {deleted: boolean, id: string}.",
|
|
155
|
+
parameters: Type.Object({
|
|
156
|
+
id: Type.String({ description: "The trigger ID to delete" }),
|
|
157
|
+
}),
|
|
158
|
+
async execute(_toolCallId: string, params: Record<string, unknown>) {
|
|
159
|
+
return {
|
|
160
|
+
content: [
|
|
161
|
+
{
|
|
162
|
+
type: "text" as const,
|
|
163
|
+
text: `Deleting trigger ${params.id}`,
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
details: { nodeMethod: "trigger.delete", params: { id: params.id } },
|
|
167
|
+
};
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
{ name: "tescmd_delete_trigger" },
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// -----------------------------------------------------------------
|
|
174
|
+
// Convenience trigger aliases
|
|
175
|
+
// -----------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
api.registerTool(
|
|
178
|
+
{
|
|
179
|
+
name: "tescmd_cabin_temp_trigger",
|
|
180
|
+
label: "Cabin Temperature Trigger",
|
|
181
|
+
description:
|
|
182
|
+
"Create a trigger on the vehicle's cabin temperature (InsideTemp). " +
|
|
183
|
+
"Shortcut for tescmd_create_trigger with field pre-set to InsideTemp. " +
|
|
184
|
+
"Example: operator='gt', value=100 fires when cabin exceeds 100°F. " +
|
|
185
|
+
"Useful for hot car alerts or climate pre-conditioning automation.",
|
|
186
|
+
parameters: Type.Object({
|
|
187
|
+
operator: stringEnum(TRIGGER_OPERATORS),
|
|
188
|
+
value: Type.Optional(Type.Number({ description: "Temperature threshold" })),
|
|
189
|
+
once: Type.Optional(Type.Boolean({ description: "Fire only once (default: false)" })),
|
|
190
|
+
cooldown_seconds: Type.Optional(
|
|
191
|
+
Type.Number({ description: "Min seconds between fires (default: 60)", minimum: 0 }),
|
|
192
|
+
),
|
|
193
|
+
}),
|
|
194
|
+
async execute(_toolCallId: string, params: Record<string, unknown>) {
|
|
195
|
+
return {
|
|
196
|
+
content: [
|
|
197
|
+
{
|
|
198
|
+
type: "text" as const,
|
|
199
|
+
text: `Creating cabin temp trigger: InsideTemp ${params.operator} ${params.value ?? "(any)"}`,
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
details: { nodeMethod: "cabin_temp.trigger", params },
|
|
203
|
+
};
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
{ name: "tescmd_cabin_temp_trigger" },
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
api.registerTool(
|
|
210
|
+
{
|
|
211
|
+
name: "tescmd_outside_temp_trigger",
|
|
212
|
+
label: "Outside Temperature Trigger",
|
|
213
|
+
description:
|
|
214
|
+
"Create a trigger on the outside ambient temperature (OutsideTemp). " +
|
|
215
|
+
"Shortcut for tescmd_create_trigger with field pre-set to OutsideTemp. " +
|
|
216
|
+
"Example: operator='lt', value=32 fires when it drops below freezing.",
|
|
217
|
+
parameters: Type.Object({
|
|
218
|
+
operator: stringEnum(TRIGGER_OPERATORS),
|
|
219
|
+
value: Type.Optional(Type.Number({ description: "Temperature threshold" })),
|
|
220
|
+
once: Type.Optional(Type.Boolean({ description: "Fire only once (default: false)" })),
|
|
221
|
+
cooldown_seconds: Type.Optional(
|
|
222
|
+
Type.Number({ description: "Min seconds between fires (default: 60)", minimum: 0 }),
|
|
223
|
+
),
|
|
224
|
+
}),
|
|
225
|
+
async execute(_toolCallId: string, params: Record<string, unknown>) {
|
|
226
|
+
return {
|
|
227
|
+
content: [
|
|
228
|
+
{
|
|
229
|
+
type: "text" as const,
|
|
230
|
+
text: `Creating outside temp trigger: OutsideTemp ${params.operator} ${params.value ?? "(any)"}`,
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
details: { nodeMethod: "outside_temp.trigger", params },
|
|
234
|
+
};
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
{ name: "tescmd_outside_temp_trigger" },
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
api.registerTool(
|
|
241
|
+
{
|
|
242
|
+
name: "tescmd_battery_trigger",
|
|
243
|
+
label: "Battery Level Trigger",
|
|
244
|
+
description:
|
|
245
|
+
"Create a trigger on the vehicle's battery level (BatteryLevel). " +
|
|
246
|
+
"Shortcut for tescmd_create_trigger with field pre-set to BatteryLevel. " +
|
|
247
|
+
"Example: operator='lt', value=20 fires when battery drops below 20%. " +
|
|
248
|
+
"Useful for low-battery alerts or charging automation.",
|
|
249
|
+
parameters: Type.Object({
|
|
250
|
+
operator: stringEnum(TRIGGER_OPERATORS),
|
|
251
|
+
value: Type.Optional(Type.Number({ description: "Battery level threshold (0-100)" })),
|
|
252
|
+
once: Type.Optional(Type.Boolean({ description: "Fire only once (default: false)" })),
|
|
253
|
+
cooldown_seconds: Type.Optional(
|
|
254
|
+
Type.Number({ description: "Min seconds between fires (default: 60)", minimum: 0 }),
|
|
255
|
+
),
|
|
256
|
+
}),
|
|
257
|
+
async execute(_toolCallId: string, params: Record<string, unknown>) {
|
|
258
|
+
return {
|
|
259
|
+
content: [
|
|
260
|
+
{
|
|
261
|
+
type: "text" as const,
|
|
262
|
+
text: `Creating battery trigger: BatteryLevel ${params.operator} ${params.value ?? "(any)"}`,
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
details: { nodeMethod: "battery.trigger", params },
|
|
266
|
+
};
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
{ name: "tescmd_battery_trigger" },
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
api.registerTool(
|
|
273
|
+
{
|
|
274
|
+
name: "tescmd_location_trigger",
|
|
275
|
+
label: "Location/Geofence Trigger",
|
|
276
|
+
description:
|
|
277
|
+
"Create a geofence trigger on the vehicle's GPS location. " +
|
|
278
|
+
"Uses the 'enter' operator to fire when the vehicle enters a radius, " +
|
|
279
|
+
"or 'leave' to fire when it exits. Requires lat, lon, and radius " +
|
|
280
|
+
"parameters. Example: operator='leave', value={lat: 37.7749, lon: -122.4194, " +
|
|
281
|
+
"radius: 500} fires when the vehicle moves more than 500 meters from the point.",
|
|
282
|
+
parameters: Type.Object({
|
|
283
|
+
operator: stringEnum(["enter", "leave"] as const),
|
|
284
|
+
lat: Type.Optional(Type.Number({ description: "Latitude of geofence center" })),
|
|
285
|
+
lon: Type.Optional(Type.Number({ description: "Longitude of geofence center" })),
|
|
286
|
+
radius: Type.Optional(
|
|
287
|
+
Type.Number({ description: "Geofence radius in meters", minimum: 0 }),
|
|
288
|
+
),
|
|
289
|
+
value: Type.Optional(
|
|
290
|
+
Type.Unknown({ description: "Alternative: pass {lat, lon, radius} as value object" }),
|
|
291
|
+
),
|
|
292
|
+
once: Type.Optional(Type.Boolean({ description: "Fire only once (default: false)" })),
|
|
293
|
+
cooldown_seconds: Type.Optional(
|
|
294
|
+
Type.Number({ description: "Min seconds between fires (default: 60)", minimum: 0 }),
|
|
295
|
+
),
|
|
296
|
+
}),
|
|
297
|
+
async execute(_toolCallId: string, params: Record<string, unknown>) {
|
|
298
|
+
return {
|
|
299
|
+
content: [
|
|
300
|
+
{
|
|
301
|
+
type: "text" as const,
|
|
302
|
+
text: `Creating location trigger: geofence ${params.operator}`,
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
details: { nodeMethod: "location.trigger", params },
|
|
306
|
+
};
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
{ name: "tescmd_location_trigger" },
|
|
310
|
+
);
|
|
311
|
+
}
|
package/tools/trunk.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trunk and frunk tools.
|
|
3
|
+
*
|
|
4
|
+
* Control the vehicle's rear trunk and front trunk (frunk) via the
|
|
5
|
+
* tescmd node. Both use the `actuate_trunk` Fleet API method with
|
|
6
|
+
* different `which_trunk` parameters.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Type } from "@sinclair/typebox";
|
|
10
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
11
|
+
|
|
12
|
+
export function registerTrunkTools(api: OpenClawPluginApi): void {
|
|
13
|
+
// -----------------------------------------------------------------
|
|
14
|
+
// tescmd_open_trunk
|
|
15
|
+
// -----------------------------------------------------------------
|
|
16
|
+
api.registerTool(
|
|
17
|
+
{
|
|
18
|
+
name: "tescmd_open_trunk",
|
|
19
|
+
label: "Open/Close Trunk",
|
|
20
|
+
description:
|
|
21
|
+
"Actuate the Tesla vehicle's rear trunk. If the trunk is closed it " +
|
|
22
|
+
"will open; if open it will close (on models with a power liftgate). " +
|
|
23
|
+
"On models without power close, the trunk will open but must be " +
|
|
24
|
+
"physically closed. Auto-wakes the vehicle if asleep.",
|
|
25
|
+
parameters: Type.Object({}),
|
|
26
|
+
async execute(_toolCallId: string, _params: Record<string, unknown>) {
|
|
27
|
+
return {
|
|
28
|
+
content: [
|
|
29
|
+
{
|
|
30
|
+
type: "text" as const,
|
|
31
|
+
text: "Invoking trunk.open on tescmd node",
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
details: { nodeMethod: "trunk.open", params: {} },
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{ name: "tescmd_open_trunk" },
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// -----------------------------------------------------------------
|
|
42
|
+
// tescmd_open_frunk
|
|
43
|
+
// -----------------------------------------------------------------
|
|
44
|
+
api.registerTool(
|
|
45
|
+
{
|
|
46
|
+
name: "tescmd_open_frunk",
|
|
47
|
+
label: "Open Frunk",
|
|
48
|
+
description:
|
|
49
|
+
"Open the Tesla vehicle's front trunk (frunk). Note: the frunk can " +
|
|
50
|
+
"only be opened remotely — it cannot be closed remotely and must be " +
|
|
51
|
+
"physically pushed down to close. Auto-wakes the vehicle if asleep.",
|
|
52
|
+
parameters: Type.Object({}),
|
|
53
|
+
async execute(_toolCallId: string, _params: Record<string, unknown>) {
|
|
54
|
+
return {
|
|
55
|
+
content: [
|
|
56
|
+
{
|
|
57
|
+
type: "text" as const,
|
|
58
|
+
text: "Invoking frunk.open on tescmd node",
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
details: { nodeMethod: "frunk.open", params: {} },
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{ name: "tescmd_open_frunk" },
|
|
66
|
+
);
|
|
67
|
+
}
|
package/tools/vehicle.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vehicle status tools — location, battery, and speed.
|
|
3
|
+
*
|
|
4
|
+
* These read-only tools query the tescmd node for real-time vehicle
|
|
5
|
+
* telemetry data. The node checks its in-memory telemetry store first
|
|
6
|
+
* (populated by the Fleet Telemetry stream) and falls back to the Tesla
|
|
7
|
+
* Fleet API if telemetry is unavailable.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Type } from "@sinclair/typebox";
|
|
11
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
12
|
+
|
|
13
|
+
export function registerVehicleTools(api: OpenClawPluginApi): void {
|
|
14
|
+
// -----------------------------------------------------------------
|
|
15
|
+
// tescmd_get_location
|
|
16
|
+
// -----------------------------------------------------------------
|
|
17
|
+
api.registerTool(
|
|
18
|
+
{
|
|
19
|
+
name: "tescmd_get_location",
|
|
20
|
+
label: "Get Vehicle Location",
|
|
21
|
+
description:
|
|
22
|
+
"Get the Tesla vehicle's current GPS location, heading, and speed. " +
|
|
23
|
+
"Returns {latitude, longitude, heading, speed} — coordinates in decimal degrees, " +
|
|
24
|
+
"heading in degrees (0-360, 0=North), speed in mph. " +
|
|
25
|
+
"\n\nWhen to use: User asks 'where is my car?', needs directions to/from the vehicle, " +
|
|
26
|
+
"wants to check if the car is at a specific location, or you need proximity info " +
|
|
27
|
+
"for a geofence decision. Also useful before setting up a location trigger. " +
|
|
28
|
+
"\n\nData source: Real-time telemetry (instant) → Fleet API drive_state (may need wake). " +
|
|
29
|
+
"If response contains {pending: true}, data is being fetched — retry in a few seconds.",
|
|
30
|
+
parameters: Type.Object({}),
|
|
31
|
+
async execute(_toolCallId: string, _params: Record<string, unknown>) {
|
|
32
|
+
return {
|
|
33
|
+
content: [
|
|
34
|
+
{
|
|
35
|
+
type: "text" as const,
|
|
36
|
+
text: "Invoking location.get on tescmd node",
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
details: {
|
|
40
|
+
nodeMethod: "location.get",
|
|
41
|
+
params: {},
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{ name: "tescmd_get_location" },
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// -----------------------------------------------------------------
|
|
50
|
+
// tescmd_get_battery
|
|
51
|
+
// -----------------------------------------------------------------
|
|
52
|
+
api.registerTool(
|
|
53
|
+
{
|
|
54
|
+
name: "tescmd_get_battery",
|
|
55
|
+
label: "Get Battery Status",
|
|
56
|
+
description:
|
|
57
|
+
"Get the Tesla vehicle's current battery level (0-100%) and estimated " +
|
|
58
|
+
"remaining range in miles. Returns {battery_level, range_miles}. " +
|
|
59
|
+
"\n\nWhen to use: User asks about charge level or range, asks 'do I need to charge?', " +
|
|
60
|
+
"is planning a trip and needs to know range, or you need to decide whether to " +
|
|
61
|
+
"suggest starting a charge. Pair with tescmd_get_charge_state to get the full " +
|
|
62
|
+
"charging picture. " +
|
|
63
|
+
"\n\nData source: Soc/BatteryLevel telemetry (instant) → Fleet API charge_state (may need wake).",
|
|
64
|
+
parameters: Type.Object({}),
|
|
65
|
+
async execute(_toolCallId: string, _params: Record<string, unknown>) {
|
|
66
|
+
return {
|
|
67
|
+
content: [
|
|
68
|
+
{
|
|
69
|
+
type: "text" as const,
|
|
70
|
+
text: "Invoking battery.get on tescmd node",
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
details: {
|
|
74
|
+
nodeMethod: "battery.get",
|
|
75
|
+
params: {},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{ name: "tescmd_get_battery" },
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// -----------------------------------------------------------------
|
|
84
|
+
// tescmd_get_speed
|
|
85
|
+
// -----------------------------------------------------------------
|
|
86
|
+
api.registerTool(
|
|
87
|
+
{
|
|
88
|
+
name: "tescmd_get_speed",
|
|
89
|
+
label: "Get Vehicle Speed",
|
|
90
|
+
description:
|
|
91
|
+
"Get the Tesla vehicle's current speed in miles per hour (mph). " +
|
|
92
|
+
"Returns {speed_mph}. Returns 0 or null when parked. " +
|
|
93
|
+
"\n\nWhen to use: User asks if the car is moving, you need to verify the vehicle " +
|
|
94
|
+
"is parked before sending a command (some commands should only be sent when " +
|
|
95
|
+
"stationary), or for trip monitoring. " +
|
|
96
|
+
"\n\nData source: VehicleSpeed telemetry (instant) → Fleet API drive_state (may need wake).",
|
|
97
|
+
parameters: Type.Object({}),
|
|
98
|
+
async execute(_toolCallId: string, _params: Record<string, unknown>) {
|
|
99
|
+
return {
|
|
100
|
+
content: [
|
|
101
|
+
{
|
|
102
|
+
type: "text" as const,
|
|
103
|
+
text: "Invoking speed.get on tescmd node",
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
details: {
|
|
107
|
+
nodeMethod: "speed.get",
|
|
108
|
+
params: {},
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
{ name: "tescmd_get_speed" },
|
|
114
|
+
);
|
|
115
|
+
}
|