@openbrt/weclawbotctl 0.1.3 → 0.1.5
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/README.md +37 -17
- package/bin/weclawbotctl.mjs +8 -4
- package/index.mjs +148 -3
- package/lib/direct-control.mjs +3 -0
- package/openclaw.plugin.json +10 -1
- package/package.json +2 -1
- package/skills/weclawbot-curator/SKILL.md +28 -7
- package/systemd/weclawbot-openclaw-curator.service +1 -1
- package/workspace/AGENTS.md +23 -0
package/README.md
CHANGED
|
@@ -15,8 +15,9 @@ Install @openbrt/weclawbotctl and connect to my WeClawBot screen with pairing co
|
|
|
15
15
|
|
|
16
16
|
Replace `123456` with the six-digit code shown on the screen. The agent should
|
|
17
17
|
then install this npm package, bind the screen, run the online doctor check,
|
|
18
|
-
and use `weclawbotctl
|
|
19
|
-
|
|
18
|
+
and use `weclawbotctl screen` with a pre-rendered pixel document for visible
|
|
19
|
+
screen updates. Use `weclawbotctl thinking` and `weclawbotctl idle` for
|
|
20
|
+
temporary work-state overlays.
|
|
20
21
|
|
|
21
22
|
## Agent Instructions
|
|
22
23
|
|
|
@@ -51,19 +52,20 @@ weclawbotctl thinking --id "$task_id" --ttl 45
|
|
|
51
52
|
weclawbotctl idle --id "$task_id"
|
|
52
53
|
```
|
|
53
54
|
|
|
54
|
-
To put
|
|
55
|
-
|
|
55
|
+
To put text, status, diagrams, or images on the screen, render them into a
|
|
56
|
+
pre-rendered `weclawbot.screen_document.v1` first. The firmware receives pixels;
|
|
57
|
+
it does not lay out text, choose fonts, or split pages for agents.
|
|
56
58
|
|
|
57
59
|
```bash
|
|
58
60
|
weclawbotctl screen /path/to/screen-document.json
|
|
59
61
|
```
|
|
60
62
|
|
|
61
63
|
The package also includes an OpenClaw integration: the `weclawbot-curator`
|
|
62
|
-
skill,
|
|
63
|
-
`
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
skill, `weclawbot_status`, `weclawbot_validate_screen_document`,
|
|
65
|
+
`weclawbot_publish_screen_document`, `weclawbot_validate_activity`,
|
|
66
|
+
`weclawbot_publish_activity`, and a small outbound bridge service. The bridge
|
|
67
|
+
polls `weclawbot.link`; no public HTTP endpoint, port forwarding, or WeChat
|
|
68
|
+
credential is required on the OpenClaw host.
|
|
67
69
|
|
|
68
70
|
## Install from npm
|
|
69
71
|
|
|
@@ -140,10 +142,19 @@ systemctl --user enable --now weclawbot-openclaw-curator
|
|
|
140
142
|
|
|
141
143
|
The normal bridge is message-triggered by WeChat. Scheduled cards and other
|
|
142
144
|
agent-originated updates use a separately paired MQTT/TLS control channel;
|
|
143
|
-
they are never placed in a gateway mailbox.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
they are never placed in a gateway mailbox. A paired Agent can publish a
|
|
146
|
+
pre-rendered screen document immediately:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
weclawbotctl doctor --online
|
|
150
|
+
weclawbotctl screen /path/to/screen-document.json
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The plugin exposes both validators and publish tools. The validator reports
|
|
154
|
+
`direct_delivery_ready:false` when a supplied event contract says that specific
|
|
155
|
+
inbound event cannot use the live document path, but that does not disable a
|
|
156
|
+
locally paired `weclawbotctl` profile. Before saying direct screen delivery is
|
|
157
|
+
unavailable, run `weclawbotctl status` or `weclawbotctl doctor --online`.
|
|
147
158
|
|
|
148
159
|
The pairing UX deliberately requires no user-supplied Agent endpoint: choose
|
|
149
160
|
**自定义智能体** in the device configurator, then enter the six-digit code shown
|
|
@@ -194,15 +205,24 @@ weclawbotctl thinking --id "$task_id" --ttl 45
|
|
|
194
205
|
weclawbotctl idle --id "$task_id"
|
|
195
206
|
```
|
|
196
207
|
|
|
197
|
-
To put
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
208
|
+
To put content on screen, pass a pre-rendered monochrome document to the same
|
|
209
|
+
credential. The document must use the live revision in the last
|
|
210
|
+
`device_context` (an empty revision is valid for the first document), one to
|
|
211
|
+
three `mono1` pages, and a future UTC expiry. Agents may use PIL, Canvas, SVG
|
|
212
|
+
rasterization, screenshots, or any local renderer, but the MQTT payload must be
|
|
213
|
+
pixels:
|
|
201
214
|
|
|
202
215
|
```bash
|
|
203
216
|
weclawbotctl screen /path/to/screen-document.json
|
|
204
217
|
```
|
|
205
218
|
|
|
219
|
+
When the user explicitly asks to overwrite whatever is currently shown, and
|
|
220
|
+
the firmware supports forced replacement, use:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
weclawbotctl screen /path/to/screen-document.json --force
|
|
224
|
+
```
|
|
225
|
+
|
|
206
226
|
These commands use MQTT/TLS directly, publish QoS 1 without retain, and
|
|
207
227
|
never create an offline command queue. See
|
|
208
228
|
`docs/agent-direct-control-protocol.md` in the firmware repository for the
|
package/bin/weclawbotctl.mjs
CHANGED
|
@@ -198,12 +198,16 @@ async function commandUnbind(values) {
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
async function commandScreen(values) {
|
|
201
|
-
const options = parseOptions(values, { credentials: credentialsPath() });
|
|
201
|
+
const options = parseOptions(values, { credentials: credentialsPath(), force: false });
|
|
202
202
|
const file = String(options._[0] || "").trim();
|
|
203
203
|
if (!file || options._.length !== 1) {
|
|
204
|
-
throw new Error("Usage: weclawbotctl screen <document.json>");
|
|
204
|
+
throw new Error("Usage: weclawbotctl screen <document.json> [--force]");
|
|
205
205
|
}
|
|
206
206
|
const document = JSON.parse(await fs.readFile(file, "utf8"));
|
|
207
|
+
if (options.force) {
|
|
208
|
+
document.force_replace = true;
|
|
209
|
+
document.base_revision = "*";
|
|
210
|
+
}
|
|
207
211
|
const validation = validateScreenDocument(document, {
|
|
208
212
|
agent_transport: { available: true, screen_document_available: true },
|
|
209
213
|
});
|
|
@@ -216,7 +220,7 @@ async function commandScreen(values) {
|
|
|
216
220
|
kind: "screen_document",
|
|
217
221
|
document,
|
|
218
222
|
});
|
|
219
|
-
console.log(JSON.stringify({ ok: true, id: document.id, pages: document.pages.length }));
|
|
223
|
+
console.log(JSON.stringify({ ok: true, id: document.id, pages: document.pages.length, force_replace: document.force_replace === true }));
|
|
220
224
|
}
|
|
221
225
|
|
|
222
226
|
async function commandActivity(state, values) {
|
|
@@ -353,5 +357,5 @@ function usage() {
|
|
|
353
357
|
weclawbotctl unbind --yes
|
|
354
358
|
weclawbotctl thinking [--ttl seconds] [--id correlation-id]
|
|
355
359
|
weclawbotctl idle [--id correlation-id]
|
|
356
|
-
weclawbotctl screen <document.json
|
|
360
|
+
weclawbotctl screen <document.json> [--force]`);
|
|
357
361
|
}
|
package/index.mjs
CHANGED
|
@@ -1,16 +1,54 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
1
6
|
import { Type } from "typebox";
|
|
2
7
|
import { defineToolPlugin } from "openclaw/plugin-sdk/tool-plugin";
|
|
3
8
|
|
|
4
9
|
import { validateActivity } from "./lib/activity.mjs";
|
|
5
10
|
import { validateScreenDocument } from "./lib/direct-control.mjs";
|
|
11
|
+
import { normalizeCredentials, publishControl, testConnection } from "./lib/mqtt-control.mjs";
|
|
12
|
+
|
|
13
|
+
const DEFAULT_CREDENTIALS_PATH = path.join(os.homedir(), ".config", "weclawbot", "agent-mqtt.json");
|
|
6
14
|
|
|
7
|
-
// The long-running curator bridge remains a separate service.
|
|
8
|
-
//
|
|
15
|
+
// The long-running curator bridge remains a separate service. These tools keep
|
|
16
|
+
// the local agent path explicit: validate first, then publish only with the
|
|
17
|
+
// user's paired MQTT credential.
|
|
9
18
|
export default defineToolPlugin({
|
|
10
19
|
id: "weclawbot",
|
|
11
20
|
name: "WeClawBot",
|
|
12
|
-
description: "WeClawBot screen-curation skill pack and direct
|
|
21
|
+
description: "WeClawBot screen-curation skill pack and direct MQTT control tools.",
|
|
13
22
|
tools: (tool) => [
|
|
23
|
+
tool({
|
|
24
|
+
name: "weclawbot_status",
|
|
25
|
+
label: "Check WeClawBot pairing",
|
|
26
|
+
description: "Read the local WeClawBot MQTT pairing profile and optionally test the live MQTT connection.",
|
|
27
|
+
parameters: Type.Object({
|
|
28
|
+
credentials_path: Type.Optional(Type.String()),
|
|
29
|
+
online: Type.Optional(Type.Boolean()),
|
|
30
|
+
}, { additionalProperties: false }),
|
|
31
|
+
execute: async ({ credentials_path, online }) => {
|
|
32
|
+
const file = expandPath(credentials_path || DEFAULT_CREDENTIALS_PATH);
|
|
33
|
+
const payload = await readCredentials(file);
|
|
34
|
+
if (!payload) {
|
|
35
|
+
return { ok: false, paired: false, credentials_path: file, error: "not_paired" };
|
|
36
|
+
}
|
|
37
|
+
const config = normalizeCredentials(payload);
|
|
38
|
+
const result = {
|
|
39
|
+
ok: true,
|
|
40
|
+
paired: true,
|
|
41
|
+
credentials_path: file,
|
|
42
|
+
binding: payload.binding || {},
|
|
43
|
+
mqtt: maskedMqtt(config, payload.mqtt?.topics || {}),
|
|
44
|
+
};
|
|
45
|
+
if (online) {
|
|
46
|
+
await testConnection(payload);
|
|
47
|
+
result.online = true;
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
},
|
|
51
|
+
}),
|
|
14
52
|
tool({
|
|
15
53
|
name: "weclawbot_validate_screen_document",
|
|
16
54
|
label: "Validate WeClawBot screen document",
|
|
@@ -21,6 +59,43 @@ export default defineToolPlugin({
|
|
|
21
59
|
}, { additionalProperties: false }),
|
|
22
60
|
execute: ({ document, device_context }) => validateScreenDocument(document, device_context),
|
|
23
61
|
}),
|
|
62
|
+
tool({
|
|
63
|
+
name: "weclawbot_publish_screen_document",
|
|
64
|
+
label: "Publish WeClawBot screen document",
|
|
65
|
+
description: "Validate and publish a pre-rendered mono1 screen document through the paired local MQTT profile. The document must already contain pixels; this tool does not lay out text.",
|
|
66
|
+
parameters: Type.Object({
|
|
67
|
+
document: Type.Any(),
|
|
68
|
+
device_context: Type.Optional(Type.Any()),
|
|
69
|
+
credentials_path: Type.Optional(Type.String()),
|
|
70
|
+
force_replace: Type.Optional(Type.Boolean()),
|
|
71
|
+
}, { additionalProperties: false }),
|
|
72
|
+
execute: async ({ document, device_context, credentials_path, force_replace }) => {
|
|
73
|
+
const outbound = cloneObject(document);
|
|
74
|
+
if (force_replace) {
|
|
75
|
+
outbound.force_replace = true;
|
|
76
|
+
outbound.base_revision = "*";
|
|
77
|
+
}
|
|
78
|
+
const validation = validateScreenDocument(outbound, device_context || {
|
|
79
|
+
agent_transport: { available: true, screen_document_available: true },
|
|
80
|
+
});
|
|
81
|
+
if (!validation.ok) {
|
|
82
|
+
return { ok: false, published: false, errors: validation.errors, validation };
|
|
83
|
+
}
|
|
84
|
+
await publishControl(await requireCredentials(credentials_path), {
|
|
85
|
+
schema: "weclawbot.control.v1",
|
|
86
|
+
id: `screen_${crypto.randomUUID()}`,
|
|
87
|
+
kind: "screen_document",
|
|
88
|
+
document: outbound,
|
|
89
|
+
});
|
|
90
|
+
return {
|
|
91
|
+
ok: true,
|
|
92
|
+
published: true,
|
|
93
|
+
id: outbound.id,
|
|
94
|
+
pages: outbound.pages.length,
|
|
95
|
+
force_replace: outbound.force_replace === true,
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
24
99
|
tool({
|
|
25
100
|
name: "weclawbot_validate_activity",
|
|
26
101
|
label: "Validate WeClawBot activity",
|
|
@@ -31,5 +106,75 @@ export default defineToolPlugin({
|
|
|
31
106
|
}, { additionalProperties: false }),
|
|
32
107
|
execute: ({ activity, device_context }) => validateActivity(activity, device_context),
|
|
33
108
|
}),
|
|
109
|
+
tool({
|
|
110
|
+
name: "weclawbot_publish_activity",
|
|
111
|
+
label: "Publish WeClawBot activity",
|
|
112
|
+
description: "Validate and publish a short-lived thinking or idle activity through the paired local MQTT profile.",
|
|
113
|
+
parameters: Type.Object({
|
|
114
|
+
activity: Type.Any(),
|
|
115
|
+
device_context: Type.Optional(Type.Any()),
|
|
116
|
+
credentials_path: Type.Optional(Type.String()),
|
|
117
|
+
}, { additionalProperties: false }),
|
|
118
|
+
execute: async ({ activity, device_context, credentials_path }) => {
|
|
119
|
+
const validation = validateActivity(activity, device_context || {
|
|
120
|
+
agent_transport: { available: true, activity_available: true },
|
|
121
|
+
});
|
|
122
|
+
if (!validation.ok) {
|
|
123
|
+
return { ok: false, published: false, errors: validation.errors, validation };
|
|
124
|
+
}
|
|
125
|
+
await publishControl(await requireCredentials(credentials_path), {
|
|
126
|
+
schema: "weclawbot.control.v1",
|
|
127
|
+
id: `activity_${crypto.randomUUID()}`,
|
|
128
|
+
kind: "activity",
|
|
129
|
+
activity,
|
|
130
|
+
});
|
|
131
|
+
return {
|
|
132
|
+
ok: true,
|
|
133
|
+
published: true,
|
|
134
|
+
state: activity.state,
|
|
135
|
+
correlation_id: activity.correlation_id,
|
|
136
|
+
};
|
|
137
|
+
},
|
|
138
|
+
}),
|
|
34
139
|
],
|
|
35
140
|
});
|
|
141
|
+
|
|
142
|
+
async function requireCredentials(credentialsPath) {
|
|
143
|
+
const file = expandPath(credentialsPath || DEFAULT_CREDENTIALS_PATH);
|
|
144
|
+
const payload = await readCredentials(file);
|
|
145
|
+
if (!payload) throw new Error(`WeClawBot is not paired. Run: weclawbotctl bind <six-digit-code>`);
|
|
146
|
+
return payload;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function readCredentials(file) {
|
|
150
|
+
try {
|
|
151
|
+
const payload = JSON.parse(await fs.readFile(file, "utf8"));
|
|
152
|
+
return payload && typeof payload === "object" ? payload : null;
|
|
153
|
+
} catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function expandPath(value) {
|
|
159
|
+
const raw = String(value || DEFAULT_CREDENTIALS_PATH);
|
|
160
|
+
return raw.startsWith("~/") ? path.join(os.homedir(), raw.slice(2)) : raw;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function cloneObject(value) {
|
|
164
|
+
if (!value || typeof value !== "object") throw new Error("document must be an object");
|
|
165
|
+
return JSON.parse(JSON.stringify(value));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function maskedMqtt(config, topics) {
|
|
169
|
+
return {
|
|
170
|
+
url: config.url,
|
|
171
|
+
client_id: config.clientId,
|
|
172
|
+
username: config.username,
|
|
173
|
+
password: "********",
|
|
174
|
+
topics: {
|
|
175
|
+
control: config.controlTopic,
|
|
176
|
+
events: topics.events || "",
|
|
177
|
+
status: topics.status || "",
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
package/lib/direct-control.mjs
CHANGED
|
@@ -41,6 +41,9 @@ export function validateScreenDocument(value, suppliedContext) {
|
|
|
41
41
|
if (document.kind !== "replace") errors.push("kind must be replace");
|
|
42
42
|
if (!string(document.id)) errors.push("id is required");
|
|
43
43
|
if (typeof document.base_revision !== "string") errors.push("base_revision must be a string (empty for the first document)");
|
|
44
|
+
if ("force_replace" in document && typeof document.force_replace !== "boolean") {
|
|
45
|
+
errors.push("force_replace must be a boolean when present");
|
|
46
|
+
}
|
|
44
47
|
if (!isFutureIso(document.expires_at)) errors.push("expires_at must be a future UTC RFC3339 timestamp");
|
|
45
48
|
if (!Array.isArray(document.pages) || document.pages.length < 1 || document.pages.length > viewport.max_pages) {
|
|
46
49
|
errors.push(`pages must contain 1..${viewport.max_pages} items`);
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "weclawbot",
|
|
3
3
|
"name": "WeClawBot",
|
|
4
|
-
"description": "Lets OpenClaw
|
|
4
|
+
"description": "Lets OpenClaw pair with and push content to a WeClawBot screen over MQTT.",
|
|
5
|
+
"contracts": {
|
|
6
|
+
"tools": [
|
|
7
|
+
"weclawbot_status",
|
|
8
|
+
"weclawbot_validate_screen_document",
|
|
9
|
+
"weclawbot_publish_screen_document",
|
|
10
|
+
"weclawbot_validate_activity",
|
|
11
|
+
"weclawbot_publish_activity"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
5
14
|
"skills": [
|
|
6
15
|
"./skills"
|
|
7
16
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openbrt/weclawbotctl",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "WeClawBot pairing and screen-control CLI for local AI agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
],
|
|
36
36
|
"bin": {
|
|
37
37
|
"weclawbot-byoa-bind": "bin/weclawbot-byoa-bind.mjs",
|
|
38
|
+
"weclawbot-openclaw-bridge": "bin/weclawbot-openclaw-bridge.mjs",
|
|
38
39
|
"weclawbotctl": "bin/weclawbotctl.mjs"
|
|
39
40
|
},
|
|
40
41
|
"exports": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: weclawbot-curator
|
|
3
|
-
description: Curate
|
|
3
|
+
description: Curate WeClawBot screen events and guide direct MQTT pushes for paired physical screens.
|
|
4
4
|
metadata: { "openclaw": { "always": true } }
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -35,11 +35,32 @@ away the relationship.
|
|
|
35
35
|
|
|
36
36
|
## Direct agent control
|
|
37
37
|
|
|
38
|
-
`wechat_transport` describes the
|
|
39
|
-
inbound only.
|
|
38
|
+
`wechat_transport` describes the official-mode iLink long-poll event path. It
|
|
39
|
+
is inbound only. In BYOA mode it may be advertised as `mode=disabled` and
|
|
40
|
+
`direction=ignored`; do not use firmware WeChat as an ingress or reply channel.
|
|
41
|
+
Never claim it lets an agent push a periodic or scheduled card.
|
|
42
|
+
|
|
43
|
+
`agent_transport.available` inside an inbound curator event is the live
|
|
44
|
+
firmware contract for that event. It is not the authority for a locally paired
|
|
45
|
+
`weclawbotctl` profile. If a user directly asks to send text, a status card, a
|
|
46
|
+
timer, or other agent-originated content to the screen, first run
|
|
47
|
+
`weclawbotctl status` or `weclawbotctl doctor --online`, or call
|
|
48
|
+
`weclawbot_status` with `online:true`. If the profile is paired and online,
|
|
49
|
+
render the content into a `weclawbot.screen_document.v1` pixel document and
|
|
50
|
+
publish it with:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
weclawbotctl screen /path/to/screen-document.json
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
or call `weclawbot_publish_screen_document` with the same document. Do not use
|
|
57
|
+
OpenClaw Canvas for requests that mention WeClawBot, the physical screen, or
|
|
58
|
+
“屏上”; Canvas is an OpenClaw UI surface, not the ESP32 e-paper display. Do
|
|
59
|
+
not send raw text to firmware. The agent owns text layout, font choice, image
|
|
60
|
+
rasterization, and page splitting; the device consumes pixels.
|
|
40
61
|
|
|
41
|
-
|
|
42
|
-
|
|
62
|
+
Only return the normal WeChat decision shape below when processing an explicit
|
|
63
|
+
`WECLAWBOT_CURATOR_EVENT` envelope.
|
|
43
64
|
|
|
44
65
|
When a paired device advertises `agent_transport.available=true`, an external
|
|
45
66
|
agent may publish a complete `weclawbot.screen_document.v1` over the live
|
|
@@ -56,8 +77,8 @@ model API key.
|
|
|
56
77
|
Treat “发到屏上” as one capability regardless of its origin. A WeChat event
|
|
57
78
|
has a reply target: return `user_reply` only when it genuinely helps the
|
|
58
79
|
sender. A timer, automation, or direct Agent request has no WeChat reply
|
|
59
|
-
target: publish
|
|
60
|
-
`rejected` event as
|
|
80
|
+
target: publish a validated screen document and use the device `applied` or
|
|
81
|
+
`rejected` event as the result.
|
|
61
82
|
|
|
62
83
|
## Thinking activity
|
|
63
84
|
|
|
@@ -6,7 +6,7 @@ Wants=network-online.target
|
|
|
6
6
|
[Service]
|
|
7
7
|
Type=simple
|
|
8
8
|
EnvironmentFile=%h/.config/weclawbot/openclaw-curator.env
|
|
9
|
-
ExecStart
|
|
9
|
+
ExecStart=%h/.npm-global/bin/weclawbot-openclaw-bridge
|
|
10
10
|
Restart=always
|
|
11
11
|
RestartSec=3
|
|
12
12
|
TimeoutStopSec=30
|
package/workspace/AGENTS.md
CHANGED
|
@@ -17,3 +17,26 @@ details in `note.body`; do not replace `note` with `content`, `note_name`, or a
|
|
|
17
17
|
wrapper object. For a list, preserve existing categories and group new items by
|
|
18
18
|
meaning. For greetings, acknowledgements, or content with no future value, use
|
|
19
19
|
`ignore` or `reply_only`.
|
|
20
|
+
|
|
21
|
+
## Direct Screen Requests
|
|
22
|
+
|
|
23
|
+
When the user asks to put something on the WeClawBot screen, treat WeClawBot
|
|
24
|
+
as the physical ESP32 e-paper display, not OpenClaw Canvas. First check the
|
|
25
|
+
local pairing:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
weclawbotctl status
|
|
29
|
+
weclawbotctl doctor --online
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
If it is paired and online, render the requested text, status, image, or chart
|
|
33
|
+
into a `weclawbot.screen_document.v1` with 1-bit pages for the firmware
|
|
34
|
+
content viewport, then publish it:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
weclawbotctl screen /path/to/screen-document.json
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The firmware receives pixels. Do not send raw text to firmware, and do not
|
|
41
|
+
answer that direct delivery is unavailable before checking the local
|
|
42
|
+
`weclawbotctl` profile.
|