ha-opencode 0.1.0
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 +315 -0
- package/blueprints/opencode_permission_response.yaml +81 -0
- package/blueprints/opencode_state_notifications.yaml +191 -0
- package/dist/cleanup.d.ts +30 -0
- package/dist/cleanup.d.ts.map +1 -0
- package/dist/cleanup.js +125 -0
- package/dist/cleanup.js.map +1 -0
- package/dist/commands.d.ts +24 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +309 -0
- package/dist/commands.js.map +1 -0
- package/dist/config.d.ts +28 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +18 -0
- package/dist/config.js.map +1 -0
- package/dist/discovery.d.ts +129 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +257 -0
- package/dist/discovery.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +175 -0
- package/dist/index.js.map +1 -0
- package/dist/mqtt.d.ts +16 -0
- package/dist/mqtt.d.ts.map +1 -0
- package/dist/mqtt.js +141 -0
- package/dist/mqtt.js.map +1 -0
- package/dist/notify.d.ts +11 -0
- package/dist/notify.d.ts.map +1 -0
- package/dist/notify.js +20 -0
- package/dist/notify.js.map +1 -0
- package/dist/state.d.ts +42 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +282 -0
- package/dist/state.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { MqttWrapper, MqttWillConfig } from "./mqtt.js";
|
|
2
|
+
import type { HaConfig } from "./config.js";
|
|
3
|
+
/**
|
|
4
|
+
* Extract the unique portion of a session ID by stripping the "ses_" prefix.
|
|
5
|
+
*/
|
|
6
|
+
export declare function extractSessionIdPart(sessionId: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Generate device ID from session ID.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getDeviceIdForSession(sessionId: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Generate the availability topic for LWT configuration.
|
|
13
|
+
* This can be called before Discovery is instantiated.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getAvailabilityTopicForSession(sessionId: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Create MQTT LWT (Last Will and Testament) config for availability tracking.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createWillConfig(sessionId: string): MqttWillConfig;
|
|
20
|
+
export interface DeviceInfo {
|
|
21
|
+
identifiers: string[];
|
|
22
|
+
name: string;
|
|
23
|
+
manufacturer: string;
|
|
24
|
+
model: string;
|
|
25
|
+
sw_version: string;
|
|
26
|
+
}
|
|
27
|
+
declare const ENTITY_DEFINITIONS: readonly [{
|
|
28
|
+
readonly key: "device_id";
|
|
29
|
+
readonly name: "Device ID";
|
|
30
|
+
readonly icon: "mdi:identifier";
|
|
31
|
+
readonly hasAttributes: true;
|
|
32
|
+
}, {
|
|
33
|
+
readonly key: "state";
|
|
34
|
+
readonly name: "State";
|
|
35
|
+
readonly icon: "mdi:state-machine";
|
|
36
|
+
readonly hasAttributes: true;
|
|
37
|
+
}, {
|
|
38
|
+
readonly key: "session_title";
|
|
39
|
+
readonly name: "Session";
|
|
40
|
+
readonly icon: "mdi:message-text";
|
|
41
|
+
}, {
|
|
42
|
+
readonly key: "model";
|
|
43
|
+
readonly name: "Model";
|
|
44
|
+
readonly icon: "mdi:brain";
|
|
45
|
+
}, {
|
|
46
|
+
readonly key: "current_tool";
|
|
47
|
+
readonly name: "Current Tool";
|
|
48
|
+
readonly icon: "mdi:tools";
|
|
49
|
+
}, {
|
|
50
|
+
readonly key: "tokens_input";
|
|
51
|
+
readonly name: "Input Tokens";
|
|
52
|
+
readonly icon: "mdi:arrow-right-bold";
|
|
53
|
+
readonly unit: "tokens";
|
|
54
|
+
readonly state_class: "measurement";
|
|
55
|
+
}, {
|
|
56
|
+
readonly key: "tokens_output";
|
|
57
|
+
readonly name: "Output Tokens";
|
|
58
|
+
readonly icon: "mdi:arrow-left-bold";
|
|
59
|
+
readonly unit: "tokens";
|
|
60
|
+
readonly state_class: "measurement";
|
|
61
|
+
}, {
|
|
62
|
+
readonly key: "cost";
|
|
63
|
+
readonly name: "Cost";
|
|
64
|
+
readonly icon: "mdi:currency-usd";
|
|
65
|
+
readonly unit: "USD";
|
|
66
|
+
readonly state_class: "total_increasing";
|
|
67
|
+
}, {
|
|
68
|
+
readonly key: "last_activity";
|
|
69
|
+
readonly name: "Last Activity";
|
|
70
|
+
readonly icon: "mdi:clock-outline";
|
|
71
|
+
readonly device_class: "timestamp";
|
|
72
|
+
}, {
|
|
73
|
+
readonly key: "permission";
|
|
74
|
+
readonly name: "Permission Request";
|
|
75
|
+
readonly icon: "mdi:shield-alert";
|
|
76
|
+
readonly hasAttributes: true;
|
|
77
|
+
}];
|
|
78
|
+
export type EntityKey = (typeof ENTITY_DEFINITIONS)[number]["key"];
|
|
79
|
+
export interface PermissionInfo {
|
|
80
|
+
id: string;
|
|
81
|
+
type: string;
|
|
82
|
+
title: string;
|
|
83
|
+
sessionID: string;
|
|
84
|
+
messageID: string;
|
|
85
|
+
callID?: string;
|
|
86
|
+
pattern?: string | string[];
|
|
87
|
+
metadata: Record<string, unknown>;
|
|
88
|
+
}
|
|
89
|
+
export declare class Discovery {
|
|
90
|
+
private readonly mqtt;
|
|
91
|
+
private readonly haConfig;
|
|
92
|
+
readonly deviceId: string;
|
|
93
|
+
private device;
|
|
94
|
+
private readonly stateTopicBase;
|
|
95
|
+
private readonly sessionId;
|
|
96
|
+
private readonly projectName;
|
|
97
|
+
constructor(mqtt: MqttWrapper, haConfig: HaConfig, sessionId: string, projectName: string);
|
|
98
|
+
/**
|
|
99
|
+
* Update the device friendly name when session title becomes available.
|
|
100
|
+
* This re-publishes all entity configs with the updated device name.
|
|
101
|
+
*/
|
|
102
|
+
updateDeviceName(title: string): Promise<void>;
|
|
103
|
+
registerDevice(): Promise<void>;
|
|
104
|
+
private registerEntity;
|
|
105
|
+
getStateTopic(key: EntityKey): string;
|
|
106
|
+
getAttributesTopic(key: EntityKey): string;
|
|
107
|
+
getCommandTopic(): string;
|
|
108
|
+
getResponseTopic(): string;
|
|
109
|
+
getAvailabilityTopic(): string;
|
|
110
|
+
/**
|
|
111
|
+
* Publish online status. Call this after device registration.
|
|
112
|
+
*/
|
|
113
|
+
publishAvailable(): Promise<void>;
|
|
114
|
+
/**
|
|
115
|
+
* Publish offline status. Call this before graceful shutdown.
|
|
116
|
+
*/
|
|
117
|
+
publishUnavailable(): Promise<void>;
|
|
118
|
+
publishState(key: EntityKey, value: string | number): Promise<void>;
|
|
119
|
+
publishAttributes(key: EntityKey, attributes: Record<string, unknown>): Promise<void>;
|
|
120
|
+
publishDeviceInfo(): Promise<void>;
|
|
121
|
+
publishPermission(permission: PermissionInfo | null): Promise<void>;
|
|
122
|
+
unregisterDevice(): Promise<void>;
|
|
123
|
+
/**
|
|
124
|
+
* Get the list of all entity keys for cleanup purposes.
|
|
125
|
+
*/
|
|
126
|
+
static getEntityKeys(): readonly EntityKey[];
|
|
127
|
+
}
|
|
128
|
+
export {};
|
|
129
|
+
//# sourceMappingURL=discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract the unique portion of a session ID by stripping the "ses_" prefix.
|
|
3
|
+
*/
|
|
4
|
+
export function extractSessionIdPart(sessionId) {
|
|
5
|
+
return sessionId.replace(/^ses_/, "");
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Generate device ID from session ID.
|
|
9
|
+
*/
|
|
10
|
+
export function getDeviceIdForSession(sessionId) {
|
|
11
|
+
return `opencode_${extractSessionIdPart(sessionId)}`;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Generate the availability topic for LWT configuration.
|
|
15
|
+
* This can be called before Discovery is instantiated.
|
|
16
|
+
*/
|
|
17
|
+
export function getAvailabilityTopicForSession(sessionId) {
|
|
18
|
+
const deviceId = getDeviceIdForSession(sessionId);
|
|
19
|
+
return `opencode/${deviceId}/availability`;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Create MQTT LWT (Last Will and Testament) config for availability tracking.
|
|
23
|
+
*/
|
|
24
|
+
export function createWillConfig(sessionId) {
|
|
25
|
+
return {
|
|
26
|
+
topic: getAvailabilityTopicForSession(sessionId),
|
|
27
|
+
payload: "offline",
|
|
28
|
+
retain: true,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const ENTITY_DEFINITIONS = [
|
|
32
|
+
{
|
|
33
|
+
key: "device_id",
|
|
34
|
+
name: "Device ID",
|
|
35
|
+
icon: "mdi:identifier",
|
|
36
|
+
hasAttributes: true,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
key: "state",
|
|
40
|
+
name: "State",
|
|
41
|
+
icon: "mdi:state-machine",
|
|
42
|
+
hasAttributes: true,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
key: "session_title",
|
|
46
|
+
name: "Session",
|
|
47
|
+
icon: "mdi:message-text",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
key: "model",
|
|
51
|
+
name: "Model",
|
|
52
|
+
icon: "mdi:brain",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
key: "current_tool",
|
|
56
|
+
name: "Current Tool",
|
|
57
|
+
icon: "mdi:tools",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
key: "tokens_input",
|
|
61
|
+
name: "Input Tokens",
|
|
62
|
+
icon: "mdi:arrow-right-bold",
|
|
63
|
+
unit: "tokens",
|
|
64
|
+
state_class: "measurement",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
key: "tokens_output",
|
|
68
|
+
name: "Output Tokens",
|
|
69
|
+
icon: "mdi:arrow-left-bold",
|
|
70
|
+
unit: "tokens",
|
|
71
|
+
state_class: "measurement",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
key: "cost",
|
|
75
|
+
name: "Cost",
|
|
76
|
+
icon: "mdi:currency-usd",
|
|
77
|
+
unit: "USD",
|
|
78
|
+
state_class: "total_increasing",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
key: "last_activity",
|
|
82
|
+
name: "Last Activity",
|
|
83
|
+
icon: "mdi:clock-outline",
|
|
84
|
+
device_class: "timestamp",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
key: "permission",
|
|
88
|
+
name: "Permission Request",
|
|
89
|
+
icon: "mdi:shield-alert",
|
|
90
|
+
hasAttributes: true,
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
export class Discovery {
|
|
94
|
+
mqtt;
|
|
95
|
+
haConfig;
|
|
96
|
+
deviceId;
|
|
97
|
+
device;
|
|
98
|
+
stateTopicBase;
|
|
99
|
+
sessionId;
|
|
100
|
+
projectName;
|
|
101
|
+
constructor(mqtt, haConfig, sessionId, projectName) {
|
|
102
|
+
this.mqtt = mqtt;
|
|
103
|
+
this.haConfig = haConfig;
|
|
104
|
+
this.sessionId = sessionId;
|
|
105
|
+
this.projectName = projectName;
|
|
106
|
+
// Create device ID from session ID (strip ses_ prefix, use full remaining ID)
|
|
107
|
+
this.deviceId = getDeviceIdForSession(sessionId);
|
|
108
|
+
// Initial device name uses project name with "Untitled" placeholder
|
|
109
|
+
this.device = {
|
|
110
|
+
identifiers: [this.deviceId],
|
|
111
|
+
name: `OpenCode - ${projectName} - Untitled`,
|
|
112
|
+
manufacturer: "OpenCode",
|
|
113
|
+
model: "AI Coding Assistant",
|
|
114
|
+
sw_version: sessionId,
|
|
115
|
+
};
|
|
116
|
+
this.stateTopicBase = `opencode/${this.deviceId}`;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Update the device friendly name when session title becomes available.
|
|
120
|
+
* This re-publishes all entity configs with the updated device name.
|
|
121
|
+
*/
|
|
122
|
+
async updateDeviceName(title) {
|
|
123
|
+
this.device.name = `OpenCode - ${this.projectName} - ${title}`;
|
|
124
|
+
// Re-register all entities with updated device info
|
|
125
|
+
await this.registerDevice();
|
|
126
|
+
}
|
|
127
|
+
async registerDevice() {
|
|
128
|
+
for (const entity of ENTITY_DEFINITIONS) {
|
|
129
|
+
await this.registerEntity(entity);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async registerEntity(entity) {
|
|
133
|
+
const uniqueId = `${this.deviceId}_${entity.key}`;
|
|
134
|
+
const configTopic = `${this.haConfig.discoveryPrefix}/sensor/${this.deviceId}/${entity.key}/config`;
|
|
135
|
+
const config = {
|
|
136
|
+
name: entity.name,
|
|
137
|
+
unique_id: uniqueId,
|
|
138
|
+
state_topic: this.getStateTopic(entity.key),
|
|
139
|
+
device: this.device,
|
|
140
|
+
// Availability tracking - entities show as unavailable when plugin disconnects
|
|
141
|
+
availability_topic: this.getAvailabilityTopic(),
|
|
142
|
+
payload_available: "online",
|
|
143
|
+
payload_not_available: "offline",
|
|
144
|
+
};
|
|
145
|
+
if (entity.icon) {
|
|
146
|
+
config.icon = entity.icon;
|
|
147
|
+
}
|
|
148
|
+
if ("unit" in entity && entity.unit) {
|
|
149
|
+
config.unit_of_measurement = entity.unit;
|
|
150
|
+
}
|
|
151
|
+
if ("state_class" in entity && entity.state_class) {
|
|
152
|
+
config.state_class = entity.state_class;
|
|
153
|
+
}
|
|
154
|
+
if ("device_class" in entity && entity.device_class) {
|
|
155
|
+
config.device_class = entity.device_class;
|
|
156
|
+
}
|
|
157
|
+
if ("hasAttributes" in entity && entity.hasAttributes) {
|
|
158
|
+
config.json_attributes_topic = this.getAttributesTopic(entity.key);
|
|
159
|
+
}
|
|
160
|
+
await this.mqtt.publish(configTopic, config, true);
|
|
161
|
+
}
|
|
162
|
+
getStateTopic(key) {
|
|
163
|
+
return `${this.stateTopicBase}/${key}`;
|
|
164
|
+
}
|
|
165
|
+
getAttributesTopic(key) {
|
|
166
|
+
return `${this.stateTopicBase}/${key}/attributes`;
|
|
167
|
+
}
|
|
168
|
+
getCommandTopic() {
|
|
169
|
+
return `${this.stateTopicBase}/command`;
|
|
170
|
+
}
|
|
171
|
+
getResponseTopic() {
|
|
172
|
+
return `${this.stateTopicBase}/response`;
|
|
173
|
+
}
|
|
174
|
+
getAvailabilityTopic() {
|
|
175
|
+
return `${this.stateTopicBase}/availability`;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Publish online status. Call this after device registration.
|
|
179
|
+
*/
|
|
180
|
+
async publishAvailable() {
|
|
181
|
+
await this.mqtt.publish(this.getAvailabilityTopic(), "online", true);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Publish offline status. Call this before graceful shutdown.
|
|
185
|
+
*/
|
|
186
|
+
async publishUnavailable() {
|
|
187
|
+
await this.mqtt.publish(this.getAvailabilityTopic(), "offline", true);
|
|
188
|
+
}
|
|
189
|
+
async publishState(key, value) {
|
|
190
|
+
const topic = this.getStateTopic(key);
|
|
191
|
+
let payload;
|
|
192
|
+
if (typeof value === "number") {
|
|
193
|
+
// Use decimal formatting only for cost, integers for token counts
|
|
194
|
+
payload = key === "cost" ? value.toFixed(6) : String(value);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
payload = value;
|
|
198
|
+
}
|
|
199
|
+
await this.mqtt.publish(topic, payload, true);
|
|
200
|
+
}
|
|
201
|
+
async publishAttributes(key, attributes) {
|
|
202
|
+
const topic = this.getAttributesTopic(key);
|
|
203
|
+
await this.mqtt.publish(topic, attributes, true);
|
|
204
|
+
}
|
|
205
|
+
async publishDeviceInfo() {
|
|
206
|
+
await this.publishState("device_id", this.deviceId);
|
|
207
|
+
await this.publishAttributes("device_id", {
|
|
208
|
+
command_topic: this.getCommandTopic(),
|
|
209
|
+
response_topic: this.getResponseTopic(),
|
|
210
|
+
state_topic_base: this.stateTopicBase,
|
|
211
|
+
device_name: this.device.name,
|
|
212
|
+
session_id: this.sessionId,
|
|
213
|
+
project_name: this.projectName,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
async publishPermission(permission) {
|
|
217
|
+
if (permission) {
|
|
218
|
+
await this.publishState("permission", "pending");
|
|
219
|
+
// Sanitize metadata to ensure it's JSON-serializable (SDK may return Decimal objects)
|
|
220
|
+
let safeMetadata = {};
|
|
221
|
+
try {
|
|
222
|
+
safeMetadata = JSON.parse(JSON.stringify(permission.metadata));
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
safeMetadata = { error: "metadata not serializable" };
|
|
226
|
+
}
|
|
227
|
+
await this.publishAttributes("permission", {
|
|
228
|
+
permission_id: permission.id,
|
|
229
|
+
type: permission.type,
|
|
230
|
+
title: permission.title,
|
|
231
|
+
session_id: permission.sessionID,
|
|
232
|
+
message_id: permission.messageID,
|
|
233
|
+
call_id: permission.callID || null,
|
|
234
|
+
pattern: permission.pattern || null,
|
|
235
|
+
metadata: safeMetadata,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
await this.publishState("permission", "none");
|
|
240
|
+
await this.publishAttributes("permission", {});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async unregisterDevice() {
|
|
244
|
+
// Publish empty config to remove entities
|
|
245
|
+
for (const entity of ENTITY_DEFINITIONS) {
|
|
246
|
+
const configTopic = `${this.haConfig.discoveryPrefix}/sensor/${this.deviceId}/${entity.key}/config`;
|
|
247
|
+
await this.mqtt.publish(configTopic, "", true);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Get the list of all entity keys for cleanup purposes.
|
|
252
|
+
*/
|
|
253
|
+
static getEntityKeys() {
|
|
254
|
+
return ENTITY_DEFINITIONS.map((e) => e.key);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAkBlD,eAAO,MAAM,mBAAmB,EAAE,MA4LjC,CAAC;AAGF,eAAe,mBAAmB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import "source-map-support/register.js";
|
|
2
|
+
import { loadConfig } from "./config.js";
|
|
3
|
+
import { connectMqtt } from "./mqtt.js";
|
|
4
|
+
import { Discovery } from "./discovery.js";
|
|
5
|
+
import { StateTracker } from "./state.js";
|
|
6
|
+
import { CommandHandler } from "./commands.js";
|
|
7
|
+
import { notify } from "./notify.js";
|
|
8
|
+
import { runCleanupInBackground } from "./cleanup.js";
|
|
9
|
+
/**
|
|
10
|
+
* Extract project name from worktree path.
|
|
11
|
+
* e.g., "/Users/foo/code/myproject" -> "myproject"
|
|
12
|
+
*/
|
|
13
|
+
function getProjectName(worktree) {
|
|
14
|
+
return worktree.split("/").pop() || "unknown";
|
|
15
|
+
}
|
|
16
|
+
export const HomeAssistantPlugin = async (input) => {
|
|
17
|
+
// Track initialization state
|
|
18
|
+
let state = null;
|
|
19
|
+
let discovery = null;
|
|
20
|
+
let commands = null;
|
|
21
|
+
let mqttClient = null;
|
|
22
|
+
let config = null;
|
|
23
|
+
let mqttConnected = false;
|
|
24
|
+
let sessionInitialized = false;
|
|
25
|
+
let currentSessionId = null;
|
|
26
|
+
const projectName = getProjectName(input.project.worktree);
|
|
27
|
+
// Cleanup function for graceful shutdown
|
|
28
|
+
const cleanup = async () => {
|
|
29
|
+
if (discovery) {
|
|
30
|
+
try {
|
|
31
|
+
await discovery.publishUnavailable();
|
|
32
|
+
await discovery.unregisterDevice();
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Ignore errors during cleanup
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (mqttClient) {
|
|
39
|
+
try {
|
|
40
|
+
await mqttClient.close();
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Ignore errors during cleanup
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
// Register cleanup on process exit signals
|
|
48
|
+
const handleExit = () => {
|
|
49
|
+
cleanup().finally(() => process.exit(0));
|
|
50
|
+
};
|
|
51
|
+
process.once("SIGINT", handleExit);
|
|
52
|
+
process.once("SIGTERM", handleExit);
|
|
53
|
+
// Connect to MQTT in the background (non-blocking)
|
|
54
|
+
const mqttPromise = (async () => {
|
|
55
|
+
// Try to read plugin config from opencode.json
|
|
56
|
+
let jsonConfig;
|
|
57
|
+
try {
|
|
58
|
+
const configResponse = await input.client.config.get();
|
|
59
|
+
if (configResponse.data) {
|
|
60
|
+
const fullConfig = configResponse.data;
|
|
61
|
+
if (fullConfig["ha-opencode"] &&
|
|
62
|
+
typeof fullConfig["ha-opencode"] === "object") {
|
|
63
|
+
jsonConfig = fullConfig["ha-opencode"];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Config not available, will use environment variables
|
|
69
|
+
}
|
|
70
|
+
config = loadConfig(jsonConfig);
|
|
71
|
+
// Connect to MQTT without LWT initially
|
|
72
|
+
// LWT will be set up per-session once we have a session ID
|
|
73
|
+
try {
|
|
74
|
+
mqttClient = await connectMqtt(config.mqtt);
|
|
75
|
+
mqttConnected = true;
|
|
76
|
+
// Run background cleanup of stale sessions
|
|
77
|
+
runCleanupInBackground(mqttClient, {
|
|
78
|
+
maxAgeDays: 7,
|
|
79
|
+
haConfig: config.ha,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
notify("OpenCode HA Plugin", `Failed to connect to MQTT broker at ${config.mqtt.host}:${config.mqtt.port}`);
|
|
84
|
+
}
|
|
85
|
+
})();
|
|
86
|
+
/**
|
|
87
|
+
* Initialize session-specific components when we receive the first session event.
|
|
88
|
+
* This creates Discovery, StateTracker, and CommandHandler for the session.
|
|
89
|
+
*/
|
|
90
|
+
async function initializeSession(sessionId) {
|
|
91
|
+
if (!mqttClient || !config) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
// Already initialized for this session
|
|
95
|
+
if (sessionInitialized && currentSessionId === sessionId) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
currentSessionId = sessionId;
|
|
99
|
+
// Create session-specific Discovery with session ID
|
|
100
|
+
discovery = new Discovery(mqttClient, config.ha, sessionId, projectName);
|
|
101
|
+
state = new StateTracker(discovery);
|
|
102
|
+
commands = new CommandHandler(mqttClient, discovery, state, input.client, config.ha);
|
|
103
|
+
try {
|
|
104
|
+
await state.initialize();
|
|
105
|
+
await commands.start();
|
|
106
|
+
sessionInitialized = true;
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
console.error("[ha-opencode] Failed to initialize session:", err);
|
|
110
|
+
sessionInitialized = false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Extract session ID from event if available.
|
|
115
|
+
*/
|
|
116
|
+
function getSessionIdFromEvent(event) {
|
|
117
|
+
const props = event.properties;
|
|
118
|
+
// session.created / session.updated have info.id
|
|
119
|
+
if (props.info && typeof props.info === "object") {
|
|
120
|
+
const info = props.info;
|
|
121
|
+
if (typeof info.id === "string") {
|
|
122
|
+
return info.id;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// permission.updated has sessionID
|
|
126
|
+
if (typeof props.sessionID === "string") {
|
|
127
|
+
return props.sessionID;
|
|
128
|
+
}
|
|
129
|
+
// message.updated has info.sessionID
|
|
130
|
+
if (props.info && typeof props.info === "object") {
|
|
131
|
+
const info = props.info;
|
|
132
|
+
if (typeof info.sessionID === "string") {
|
|
133
|
+
return info.sessionID;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
// Return hooks immediately - they'll no-op until MQTT connects
|
|
139
|
+
return {
|
|
140
|
+
event: async ({ event }) => {
|
|
141
|
+
// Send notification when session becomes idle (task complete)
|
|
142
|
+
if (event.type === "session.idle") {
|
|
143
|
+
notify("OpenCode", "Task complete");
|
|
144
|
+
}
|
|
145
|
+
// Wait for MQTT connection on first event (with timeout)
|
|
146
|
+
if (!mqttConnected) {
|
|
147
|
+
await Promise.race([
|
|
148
|
+
mqttPromise,
|
|
149
|
+
new Promise((resolve) => setTimeout(resolve, 15000)),
|
|
150
|
+
]);
|
|
151
|
+
}
|
|
152
|
+
if (!mqttClient || !mqttConnected) {
|
|
153
|
+
return; // MQTT not ready, skip event
|
|
154
|
+
}
|
|
155
|
+
// Try to extract session ID from event
|
|
156
|
+
const sessionId = getSessionIdFromEvent(event);
|
|
157
|
+
// Initialize session components on first session event
|
|
158
|
+
if (sessionId && !sessionInitialized) {
|
|
159
|
+
await initializeSession(sessionId);
|
|
160
|
+
}
|
|
161
|
+
if (!state || !sessionInitialized) {
|
|
162
|
+
return; // Session not ready, skip event
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
await state.handleEvent(event);
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
console.error(`[ha-opencode] Error handling event '${event.type}':`, err);
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
// Default export for OpenCode plugin loader
|
|
174
|
+
export default HomeAssistantPlugin;
|
|
175
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,gCAAgC,CAAC;AAGxC,OAAO,EAAE,UAAU,EAAsC,MAAM,aAAa,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAoB,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAEtD;;;GAGG;AACH,SAAS,cAAc,CAAC,QAAgB;IACtC,OAAO,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,SAAS,CAAC;AAChD,CAAC;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAW,KAAK,EAAE,KAAK,EAAE,EAAE;IACzD,6BAA6B;IAC7B,IAAI,KAAK,GAAwB,IAAI,CAAC;IACtC,IAAI,SAAS,GAAqB,IAAI,CAAC;IACvC,IAAI,QAAQ,GAA0B,IAAI,CAAC;IAC3C,IAAI,UAAU,GAAuB,IAAI,CAAC;IAC1C,IAAI,MAAM,GAAwB,IAAI,CAAC;IACvC,IAAI,aAAa,GAAG,KAAK,CAAC;IAC1B,IAAI,kBAAkB,GAAG,KAAK,CAAC;IAC/B,IAAI,gBAAgB,GAAkB,IAAI,CAAC;IAE3C,MAAM,WAAW,GAAG,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE3D,yCAAyC;IACzC,MAAM,OAAO,GAAG,KAAK,IAAI,EAAE;QACzB,IAAI,SAAS,EAAE,CAAC;YACd,IAAI,CAAC;gBACH,MAAM,SAAS,CAAC,kBAAkB,EAAE,CAAC;gBACrC,MAAM,SAAS,CAAC,gBAAgB,EAAE,CAAC;YACrC,CAAC;YAAC,MAAM,CAAC;gBACP,+BAA+B;YACjC,CAAC;QACH,CAAC;QACD,IAAI,UAAU,EAAE,CAAC;YACf,IAAI,CAAC;gBACH,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC;YAC3B,CAAC;YAAC,MAAM,CAAC;gBACP,+BAA+B;YACjC,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,2CAA2C;IAC3C,MAAM,UAAU,GAAG,GAAG,EAAE;QACtB,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3C,CAAC,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IACnC,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAEpC,mDAAmD;IACnD,MAAM,WAAW,GAAG,CAAC,KAAK,IAAI,EAAE;QAC9B,+CAA+C;QAC/C,IAAI,UAAkC,CAAC;QACvC,IAAI,CAAC;YACH,MAAM,cAAc,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;YACvD,IAAI,cAAc,CAAC,IAAI,EAAE,CAAC;gBACxB,MAAM,UAAU,GAAG,cAAc,CAAC,IAA+B,CAAC;gBAClE,IACE,UAAU,CAAC,aAAa,CAAC;oBACzB,OAAO,UAAU,CAAC,aAAa,CAAC,KAAK,QAAQ,EAC7C,CAAC;oBACD,UAAU,GAAG,UAAU,CAAC,aAAa,CAAe,CAAC;gBACvD,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,uDAAuD;QACzD,CAAC;QAED,MAAM,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;QAEhC,wCAAwC;QACxC,2DAA2D;QAC3D,IAAI,CAAC;YACH,UAAU,GAAG,MAAM,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC5C,aAAa,GAAG,IAAI,CAAC;YAErB,2CAA2C;YAC3C,sBAAsB,CAAC,UAAU,EAAE;gBACjC,UAAU,EAAE,CAAC;gBACb,QAAQ,EAAE,MAAM,CAAC,EAAE;aACpB,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CACJ,oBAAoB,EACpB,uCAAuC,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAC9E,CAAC;QACJ,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;IAEL;;;OAGG;IACH,KAAK,UAAU,iBAAiB,CAAC,SAAiB;QAChD,IAAI,CAAC,UAAU,IAAI,CAAC,MAAM,EAAE,CAAC;YAC3B,OAAO;QACT,CAAC;QAED,uCAAuC;QACvC,IAAI,kBAAkB,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;YACzD,OAAO;QACT,CAAC;QAED,gBAAgB,GAAG,SAAS,CAAC;QAE7B,oDAAoD;QACpD,SAAS,GAAG,IAAI,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;QACzE,KAAK,GAAG,IAAI,YAAY,CAAC,SAAS,CAAC,CAAC;QACpC,QAAQ,GAAG,IAAI,cAAc,CAC3B,UAAU,EACV,SAAS,EACT,KAAK,EACL,KAAK,CAAC,MAAM,EACZ,MAAM,CAAC,EAAE,CACV,CAAC;QAEF,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,UAAU,EAAE,CAAC;YACzB,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;YACvB,kBAAkB,GAAG,IAAI,CAAC;QAC5B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,6CAA6C,EAAE,GAAG,CAAC,CAAC;YAClE,kBAAkB,GAAG,KAAK,CAAC;QAC7B,CAAC;IACH,CAAC;IAED;;OAEG;IACH,SAAS,qBAAqB,CAAC,KAAY;QACzC,MAAM,KAAK,GAAG,KAAK,CAAC,UAAqC,CAAC;QAE1D,iDAAiD;QACjD,IAAI,KAAK,CAAC,IAAI,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACjD,MAAM,IAAI,GAAG,KAAK,CAAC,IAA+B,CAAC;YACnD,IAAI,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;gBAChC,OAAO,IAAI,CAAC,EAAE,CAAC;YACjB,CAAC;QACH,CAAC;QAED,mCAAmC;QACnC,IAAI,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;YACxC,OAAO,KAAK,CAAC,SAAS,CAAC;QACzB,CAAC;QAED,qCAAqC;QACrC,IAAI,KAAK,CAAC,IAAI,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACjD,MAAM,IAAI,GAAG,KAAK,CAAC,IAA+B,CAAC;YACnD,IAAI,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;gBACvC,OAAO,IAAI,CAAC,SAAS,CAAC;YACxB,CAAC;QACH,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED,+DAA+D;IAC/D,OAAO;QACL,KAAK,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;YACzB,8DAA8D;YAC9D,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;gBAClC,MAAM,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;YACtC,CAAC;YAED,yDAAyD;YACzD,IAAI,CAAC,aAAa,EAAE,CAAC;gBACnB,MAAM,OAAO,CAAC,IAAI,CAAC;oBACjB,WAAW;oBACX,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;iBACrD,CAAC,CAAC;YACL,CAAC;YAED,IAAI,CAAC,UAAU,IAAI,CAAC,aAAa,EAAE,CAAC;gBAClC,OAAO,CAAC,6BAA6B;YACvC,CAAC;YAED,uCAAuC;YACvC,MAAM,SAAS,GAAG,qBAAqB,CAAC,KAAK,CAAC,CAAC;YAE/C,uDAAuD;YACvD,IAAI,SAAS,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBACrC,MAAM,iBAAiB,CAAC,SAAS,CAAC,CAAC;YACrC,CAAC;YAED,IAAI,CAAC,KAAK,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBAClC,OAAO,CAAC,gCAAgC;YAC1C,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YACjC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CACX,uCAAuC,KAAK,CAAC,IAAI,IAAI,EACrD,GAAG,CACJ,CAAC;YACJ,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,4CAA4C;AAC5C,eAAe,mBAAmB,CAAC"}
|
package/dist/mqtt.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MqttConfig } from "./config.js";
|
|
2
|
+
export type MessageHandler = (topic: string, payload: string) => void;
|
|
3
|
+
export interface MqttWillConfig {
|
|
4
|
+
topic: string;
|
|
5
|
+
payload: string;
|
|
6
|
+
retain: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface MqttWrapper {
|
|
9
|
+
publish(topic: string, payload: string | object, retain?: boolean): Promise<void>;
|
|
10
|
+
subscribe(topic: string, handler: MessageHandler): Promise<void>;
|
|
11
|
+
unsubscribe(topic: string): Promise<void>;
|
|
12
|
+
isConnected(): boolean;
|
|
13
|
+
close(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export declare function connectMqtt(config: MqttConfig, will?: MqttWillConfig): Promise<MqttWrapper>;
|
|
16
|
+
//# sourceMappingURL=mqtt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mqtt.d.ts","sourceRoot":"","sources":["../src/mqtt.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AAEtE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClF,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,WAAW,IAAI,OAAO,CAAC;IACvB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,CAoDjG"}
|