@karmaniverous/jeeves-watcher-openclaw 0.5.4 → 0.5.6
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/cli.js +52 -0
- package/dist/index.js +97 -72
- package/dist/src/promptInjection.d.ts +5 -0
- package/dist/src/toolsWriter.d.ts +20 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -199,10 +199,62 @@ function uninstall() {
|
|
|
199
199
|
writeJson(configPath, config);
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
|
+
// Clean up TOOLS.md watcher section
|
|
203
|
+
cleanupToolsMd(home, configPath);
|
|
202
204
|
console.log();
|
|
203
205
|
console.log('✅ Plugin uninstalled successfully.');
|
|
204
206
|
console.log(' Restart the OpenClaw gateway to complete removal.');
|
|
205
207
|
}
|
|
208
|
+
/** Resolve the workspace directory from OpenClaw config. */
|
|
209
|
+
function resolveWorkspaceDir(home, configPath) {
|
|
210
|
+
const config = readJson(configPath);
|
|
211
|
+
if (!config)
|
|
212
|
+
return null;
|
|
213
|
+
// Check agents.defaults.workspace
|
|
214
|
+
const agents = config.agents;
|
|
215
|
+
const defaults = agents?.defaults;
|
|
216
|
+
const workspace = defaults?.workspace;
|
|
217
|
+
if (workspace) {
|
|
218
|
+
return resolve(workspace.replace(/^~/, homedir()));
|
|
219
|
+
}
|
|
220
|
+
// Default workspace location
|
|
221
|
+
return join(home, 'workspace');
|
|
222
|
+
}
|
|
223
|
+
/** Remove the ## Watcher section from TOOLS.md on uninstall. */
|
|
224
|
+
function cleanupToolsMd(home, configPath) {
|
|
225
|
+
const workspaceDir = resolveWorkspaceDir(home, configPath);
|
|
226
|
+
if (!workspaceDir)
|
|
227
|
+
return;
|
|
228
|
+
const toolsPath = join(workspaceDir, 'TOOLS.md');
|
|
229
|
+
if (!existsSync(toolsPath))
|
|
230
|
+
return;
|
|
231
|
+
let content = readFileSync(toolsPath, 'utf8');
|
|
232
|
+
// Remove ## Watcher section (from ## Watcher to next ## or # or EOF)
|
|
233
|
+
const watcherRe = /^## Watcher\n[\s\S]*?(?=\n## |\n# |$(?![\s\S]))/m;
|
|
234
|
+
if (!watcherRe.test(content))
|
|
235
|
+
return;
|
|
236
|
+
content = content.replace(watcherRe, '').replace(/\n{3,}/g, '\n\n');
|
|
237
|
+
// If # Jeeves Platform Tools has no remaining ## sections, remove it too
|
|
238
|
+
const platformH1 = '# Jeeves Platform Tools';
|
|
239
|
+
if (content.includes(platformH1)) {
|
|
240
|
+
const h1Idx = content.indexOf(platformH1);
|
|
241
|
+
const afterH1 = content.slice(h1Idx + platformH1.length);
|
|
242
|
+
// Check if there's a ## before the next # or EOF
|
|
243
|
+
const nextH2Match = afterH1.match(/^## /m);
|
|
244
|
+
const nextH1Match = afterH1.match(/^# /m);
|
|
245
|
+
const h2Pos = nextH2Match ? afterH1.indexOf(nextH2Match[0]) : Infinity;
|
|
246
|
+
const h1Pos = nextH1Match ? afterH1.indexOf(nextH1Match[0]) : Infinity;
|
|
247
|
+
if (h2Pos >= h1Pos) {
|
|
248
|
+
// No child ## sections remain — remove the empty H1
|
|
249
|
+
content =
|
|
250
|
+
content.slice(0, h1Idx) + content.slice(h1Idx + platformH1.length);
|
|
251
|
+
content = content.replace(/^\n{2,}/, '').replace(/\n{3,}/g, '\n\n');
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
content = content.trim() + '\n';
|
|
255
|
+
writeFileSync(toolsPath, content);
|
|
256
|
+
console.log('\u2713 Cleaned up TOOLS.md (removed Watcher section)');
|
|
257
|
+
}
|
|
206
258
|
// Main
|
|
207
259
|
const command = process.argv[2];
|
|
208
260
|
switch (command) {
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* @module plugin/helpers
|
|
3
6
|
* Shared types and utility functions for the OpenClaw plugin tool registrations.
|
|
4
7
|
*/
|
|
5
8
|
const DEFAULT_API_URL = 'http://127.0.0.1:1936';
|
|
6
|
-
const DEFAULT_CACHE_TTL_MS = 30000;
|
|
7
9
|
/** Extract plugin config from the API object */
|
|
8
10
|
function getPluginConfig(api) {
|
|
9
11
|
return api.config?.plugins?.entries?.['jeeves-watcher-openclaw']?.config;
|
|
@@ -13,11 +15,6 @@ function getApiUrl(api) {
|
|
|
13
15
|
const url = getPluginConfig(api)?.apiUrl;
|
|
14
16
|
return typeof url === 'string' ? url : DEFAULT_API_URL;
|
|
15
17
|
}
|
|
16
|
-
/** Resolve the cache TTL for plugin hooks from config. */
|
|
17
|
-
function getCacheTtlMs(api) {
|
|
18
|
-
const ttl = getPluginConfig(api)?.cacheTtlMs;
|
|
19
|
-
return typeof ttl === 'number' ? ttl : DEFAULT_CACHE_TTL_MS;
|
|
20
|
-
}
|
|
21
18
|
/** Format a successful tool result. */
|
|
22
19
|
function ok(data) {
|
|
23
20
|
return {
|
|
@@ -73,14 +70,6 @@ async function postJson(url, body) {
|
|
|
73
70
|
});
|
|
74
71
|
}
|
|
75
72
|
|
|
76
|
-
let menuCache = null;
|
|
77
|
-
function isAgentBootstrapEventContext(value) {
|
|
78
|
-
if (!value || typeof value !== 'object') {
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
const v = value;
|
|
82
|
-
return Array.isArray(v.bootstrapFiles);
|
|
83
|
-
}
|
|
84
73
|
async function fetchJson(url, init) {
|
|
85
74
|
const res = await fetch(url, init);
|
|
86
75
|
if (!res.ok) {
|
|
@@ -177,68 +166,106 @@ async function generateWatcherMenu(apiUrl) {
|
|
|
177
166
|
}
|
|
178
167
|
return lines.join('\n');
|
|
179
168
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @module plugin/toolsWriter
|
|
172
|
+
* Writes the Watcher menu section directly to TOOLS.md on disk.
|
|
173
|
+
* Replaces the agent:bootstrap hook approach which was unreliable due to
|
|
174
|
+
* OpenClaw's clearInternalHooks() wiping plugin-registered hooks on startup.
|
|
175
|
+
*/
|
|
176
|
+
const REFRESH_INTERVAL_MS = 60_000;
|
|
177
|
+
let intervalHandle = null;
|
|
178
|
+
let lastWrittenMenu = '';
|
|
179
|
+
/**
|
|
180
|
+
* Resolve the workspace TOOLS.md path.
|
|
181
|
+
* Uses api.resolvePath if available, otherwise falls back to CWD.
|
|
182
|
+
*/
|
|
183
|
+
function resolveToolsPath(api) {
|
|
184
|
+
const resolvePath = api
|
|
185
|
+
.resolvePath;
|
|
186
|
+
if (typeof resolvePath === 'function') {
|
|
187
|
+
return resolvePath('TOOLS.md');
|
|
192
188
|
}
|
|
193
|
-
return
|
|
189
|
+
return resolve(process.cwd(), 'TOOLS.md');
|
|
194
190
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
191
|
+
/**
|
|
192
|
+
* Upsert the watcher section in TOOLS.md content.
|
|
193
|
+
*
|
|
194
|
+
* Strategy:
|
|
195
|
+
* - If a `## Watcher` section already exists, replace it in place.
|
|
196
|
+
* - Otherwise, prepend `# Jeeves Platform Tools\n\n## Watcher\n\n...`
|
|
197
|
+
* before any existing content.
|
|
198
|
+
*/
|
|
199
|
+
function upsertWatcherContent(existing, watcherMenu) {
|
|
200
|
+
const section = `## Watcher\n\n${watcherMenu}`;
|
|
201
|
+
// Replace existing watcher section (match from ## Watcher to next ## or # or EOF)
|
|
201
202
|
const re = /^## Watcher\n[\s\S]*?(?=\n## |\n# |$(?![\s\S]))/m;
|
|
202
|
-
if (re.test(
|
|
203
|
-
return
|
|
203
|
+
if (re.test(existing)) {
|
|
204
|
+
return existing.replace(re, section);
|
|
204
205
|
}
|
|
205
|
-
//
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
return
|
|
206
|
+
// No existing section. Prepend under a platform tools H1.
|
|
207
|
+
const platformH1 = '# Jeeves Platform Tools';
|
|
208
|
+
if (existing.includes(platformH1)) {
|
|
209
|
+
// Insert after the H1
|
|
210
|
+
const idx = existing.indexOf(platformH1) + platformH1.length;
|
|
211
|
+
return existing.slice(0, idx) + `\n\n${section}\n` + existing.slice(idx);
|
|
211
212
|
}
|
|
212
|
-
|
|
213
|
+
// Prepend platform header + watcher section before existing content
|
|
214
|
+
const trimmed = existing.trim();
|
|
215
|
+
if (trimmed.length === 0) {
|
|
216
|
+
return `${platformH1}\n\n${section}\n`;
|
|
217
|
+
}
|
|
218
|
+
return `${platformH1}\n\n${section}\n\n${trimmed}\n`;
|
|
213
219
|
}
|
|
214
220
|
/**
|
|
215
|
-
*
|
|
216
|
-
*
|
|
221
|
+
* Fetch the current watcher menu and write it to TOOLS.md if changed.
|
|
222
|
+
* Returns true if the file was updated.
|
|
217
223
|
*/
|
|
218
|
-
async function
|
|
219
|
-
const context = event?.context;
|
|
220
|
-
if (!isAgentBootstrapEventContext(context)) {
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
224
|
+
async function refreshToolsMd(api) {
|
|
223
225
|
const apiUrl = getApiUrl(api);
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
226
|
+
const menu = await generateWatcherMenu(apiUrl);
|
|
227
|
+
if (menu === lastWrittenMenu) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
const toolsPath = resolveToolsPath(api);
|
|
231
|
+
let current = '';
|
|
232
|
+
try {
|
|
233
|
+
current = await readFile(toolsPath, 'utf8');
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
// File doesn't exist yet — we'll create it
|
|
230
237
|
}
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
+
const updated = upsertWatcherContent(current, menu);
|
|
239
|
+
if (updated !== current) {
|
|
240
|
+
await writeFile(toolsPath, updated, 'utf8');
|
|
241
|
+
lastWrittenMenu = menu;
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
lastWrittenMenu = menu;
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Start the periodic TOOLS.md writer.
|
|
249
|
+
* Writes immediately on startup, then refreshes every REFRESH_INTERVAL_MS.
|
|
250
|
+
*/
|
|
251
|
+
function startToolsWriter(api) {
|
|
252
|
+
// Initial write (fire and forget — errors logged but not thrown)
|
|
253
|
+
refreshToolsMd(api).catch((err) => {
|
|
254
|
+
console.error('[jeeves-watcher] Failed to write TOOLS.md:', err);
|
|
255
|
+
});
|
|
256
|
+
// Periodic refresh
|
|
257
|
+
if (intervalHandle) {
|
|
258
|
+
clearInterval(intervalHandle);
|
|
259
|
+
}
|
|
260
|
+
intervalHandle = setInterval(() => {
|
|
261
|
+
refreshToolsMd(api).catch((err) => {
|
|
262
|
+
console.error('[jeeves-watcher] Failed to refresh TOOLS.md:', err);
|
|
263
|
+
});
|
|
264
|
+
}, REFRESH_INTERVAL_MS);
|
|
265
|
+
// Don't keep the process alive just for this interval
|
|
266
|
+
if (typeof intervalHandle === 'object' && 'unref' in intervalHandle) {
|
|
267
|
+
intervalHandle.unref();
|
|
238
268
|
}
|
|
239
|
-
const withH1 = ensurePlatformToolsSection(current);
|
|
240
|
-
const updated = upsertWatcherSection(withH1, watcherMenu);
|
|
241
|
-
toolsFile.content = updated;
|
|
242
269
|
}
|
|
243
270
|
|
|
244
271
|
/**
|
|
@@ -427,13 +454,11 @@ function registerWatcherTools(api, baseUrl) {
|
|
|
427
454
|
function register(api) {
|
|
428
455
|
const baseUrl = getApiUrl(api);
|
|
429
456
|
registerWatcherTools(api, baseUrl);
|
|
430
|
-
//
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}, { name: 'jeeves-watcher-openclaw' });
|
|
436
|
-
}
|
|
457
|
+
// Write the watcher menu to TOOLS.md on disk, refreshing periodically.
|
|
458
|
+
// This replaces the agent:bootstrap hook approach which was unreliable
|
|
459
|
+
// because OpenClaw's clearInternalHooks() wipes plugin-registered hooks
|
|
460
|
+
// during the async startup sequence.
|
|
461
|
+
startToolsWriter(api);
|
|
437
462
|
}
|
|
438
463
|
|
|
439
464
|
export { register as default };
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { type PluginApi } from './helpers.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fetches data from the watcher API and generates a Markdown menu string.
|
|
4
|
+
* The string is platform-agnostic and safe to inject into TOOLS.md.
|
|
5
|
+
*/
|
|
6
|
+
export declare function generateWatcherMenu(apiUrl: string): Promise<string>;
|
|
2
7
|
/**
|
|
3
8
|
* Hook handler for agent:bootstrap.
|
|
4
9
|
* Injects/updates the Watcher Menu into the TOOLS.md payload.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module plugin/toolsWriter
|
|
3
|
+
* Writes the Watcher menu section directly to TOOLS.md on disk.
|
|
4
|
+
* Replaces the agent:bootstrap hook approach which was unreliable due to
|
|
5
|
+
* OpenClaw's clearInternalHooks() wiping plugin-registered hooks on startup.
|
|
6
|
+
*/
|
|
7
|
+
import { type PluginApi } from './helpers.js';
|
|
8
|
+
/**
|
|
9
|
+
* Start the periodic TOOLS.md writer.
|
|
10
|
+
* Writes immediately on startup, then refreshes every REFRESH_INTERVAL_MS.
|
|
11
|
+
*/
|
|
12
|
+
export declare function startToolsWriter(api: PluginApi): void;
|
|
13
|
+
/**
|
|
14
|
+
* Force an immediate refresh (e.g., after watcher_config_apply).
|
|
15
|
+
*/
|
|
16
|
+
export declare function forceRefreshToolsMd(api: PluginApi): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Stop the periodic writer (for cleanup).
|
|
19
|
+
*/
|
|
20
|
+
export declare function stopToolsWriter(): void;
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "jeeves-watcher-openclaw",
|
|
3
3
|
"name": "Jeeves Watcher",
|
|
4
4
|
"description": "Semantic search, metadata enrichment, and instance administration for a jeeves-watcher deployment.",
|
|
5
|
-
"version": "0.5.
|
|
5
|
+
"version": "0.5.6",
|
|
6
6
|
"skills": [
|
|
7
7
|
"dist/skills/jeeves-watcher"
|
|
8
8
|
],
|
package/package.json
CHANGED