@karmaniverous/jeeves-server-openclaw 0.3.1 → 0.4.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/dist/cli.js +18897 -121
- package/dist/index.js +1752 -299
- package/dist/src/cli.d.ts +7 -0
- package/dist/src/helpers.d.ts +9 -45
- package/dist/src/index.d.ts +1 -1
- package/dist/src/serverTools.d.ts +1 -1
- package/dist/src/serviceCommands.d.ts +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -2
- package/dist/src/cli.test.d.ts +0 -1
- package/dist/src/configPatch.d.ts +0 -8
- package/dist/src/openclawPaths.d.ts +0 -14
- package/dist/src/openclawPaths.test.d.ts +0 -4
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
|
-
import
|
|
2
|
+
import 'vm';
|
|
3
3
|
import fs, { existsSync, mkdirSync, writeFileSync, readFileSync, renameSync, cpSync, rmSync } from 'node:fs';
|
|
4
|
+
import path, { join, dirname, resolve } from 'node:path';
|
|
4
5
|
import require$$0$3 from 'path';
|
|
5
6
|
import require$$0$2 from 'fs';
|
|
6
7
|
import require$$0 from 'constants';
|
|
@@ -12,8 +13,1214 @@ import { fileURLToPath } from 'node:url';
|
|
|
12
13
|
import 'node:fs/promises';
|
|
13
14
|
import process$1 from 'node:process';
|
|
14
15
|
import { execSync } from 'node:child_process';
|
|
15
|
-
import { createHmac } from 'node:crypto';
|
|
16
16
|
import { homedir } from 'node:os';
|
|
17
|
+
import { createHmac } from 'node:crypto';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @implements {IHooks}
|
|
21
|
+
*/
|
|
22
|
+
class Hooks {
|
|
23
|
+
/**
|
|
24
|
+
* @callback HookCallback
|
|
25
|
+
* @this {*|Jsep} this
|
|
26
|
+
* @param {Jsep} env
|
|
27
|
+
* @returns: void
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Adds the given callback to the list of callbacks for the given hook.
|
|
31
|
+
*
|
|
32
|
+
* The callback will be invoked when the hook it is registered for is run.
|
|
33
|
+
*
|
|
34
|
+
* One callback function can be registered to multiple hooks and the same hook multiple times.
|
|
35
|
+
*
|
|
36
|
+
* @param {string|object} name The name of the hook, or an object of callbacks keyed by name
|
|
37
|
+
* @param {HookCallback|boolean} callback The callback function which is given environment variables.
|
|
38
|
+
* @param {?boolean} [first=false] Will add the hook to the top of the list (defaults to the bottom)
|
|
39
|
+
* @public
|
|
40
|
+
*/
|
|
41
|
+
add(name, callback, first) {
|
|
42
|
+
if (typeof arguments[0] != 'string') {
|
|
43
|
+
// Multiple hook callbacks, keyed by name
|
|
44
|
+
for (let name in arguments[0]) {
|
|
45
|
+
this.add(name, arguments[0][name], arguments[1]);
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
(Array.isArray(name) ? name : [name]).forEach(function (name) {
|
|
49
|
+
this[name] = this[name] || [];
|
|
50
|
+
if (callback) {
|
|
51
|
+
this[name][first ? 'unshift' : 'push'](callback);
|
|
52
|
+
}
|
|
53
|
+
}, this);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Runs a hook invoking all registered callbacks with the given environment variables.
|
|
59
|
+
*
|
|
60
|
+
* Callbacks will be invoked synchronously and in the order in which they were registered.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} name The name of the hook.
|
|
63
|
+
* @param {Object<string, any>} env The environment variables of the hook passed to all callbacks registered.
|
|
64
|
+
* @public
|
|
65
|
+
*/
|
|
66
|
+
run(name, env) {
|
|
67
|
+
this[name] = this[name] || [];
|
|
68
|
+
this[name].forEach(function (callback) {
|
|
69
|
+
callback.call(env && env.context ? env.context : env, env);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @implements {IPlugins}
|
|
76
|
+
*/
|
|
77
|
+
class Plugins {
|
|
78
|
+
constructor(jsep) {
|
|
79
|
+
this.jsep = jsep;
|
|
80
|
+
this.registered = {};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @callback PluginSetup
|
|
85
|
+
* @this {Jsep} jsep
|
|
86
|
+
* @returns: void
|
|
87
|
+
*/
|
|
88
|
+
/**
|
|
89
|
+
* Adds the given plugin(s) to the registry
|
|
90
|
+
*
|
|
91
|
+
* @param {object} plugins
|
|
92
|
+
* @param {string} plugins.name The name of the plugin
|
|
93
|
+
* @param {PluginSetup} plugins.init The init function
|
|
94
|
+
* @public
|
|
95
|
+
*/
|
|
96
|
+
register(...plugins) {
|
|
97
|
+
plugins.forEach(plugin => {
|
|
98
|
+
if (typeof plugin !== 'object' || !plugin.name || !plugin.init) {
|
|
99
|
+
throw new Error('Invalid JSEP plugin format');
|
|
100
|
+
}
|
|
101
|
+
if (this.registered[plugin.name]) {
|
|
102
|
+
// already registered. Ignore.
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
plugin.init(this.jsep);
|
|
106
|
+
this.registered[plugin.name] = plugin;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// JavaScript Expression Parser (JSEP) 1.4.0
|
|
112
|
+
|
|
113
|
+
class Jsep {
|
|
114
|
+
/**
|
|
115
|
+
* @returns {string}
|
|
116
|
+
*/
|
|
117
|
+
static get version() {
|
|
118
|
+
// To be filled in by the template
|
|
119
|
+
return '1.4.0';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @returns {string}
|
|
124
|
+
*/
|
|
125
|
+
static toString() {
|
|
126
|
+
return 'JavaScript Expression Parser (JSEP) v' + Jsep.version;
|
|
127
|
+
}
|
|
128
|
+
// ==================== CONFIG ================================
|
|
129
|
+
/**
|
|
130
|
+
* @method addUnaryOp
|
|
131
|
+
* @param {string} op_name The name of the unary op to add
|
|
132
|
+
* @returns {Jsep}
|
|
133
|
+
*/
|
|
134
|
+
static addUnaryOp(op_name) {
|
|
135
|
+
Jsep.max_unop_len = Math.max(op_name.length, Jsep.max_unop_len);
|
|
136
|
+
Jsep.unary_ops[op_name] = 1;
|
|
137
|
+
return Jsep;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @method jsep.addBinaryOp
|
|
142
|
+
* @param {string} op_name The name of the binary op to add
|
|
143
|
+
* @param {number} precedence The precedence of the binary op (can be a float). Higher number = higher precedence
|
|
144
|
+
* @param {boolean} [isRightAssociative=false] whether operator is right-associative
|
|
145
|
+
* @returns {Jsep}
|
|
146
|
+
*/
|
|
147
|
+
static addBinaryOp(op_name, precedence, isRightAssociative) {
|
|
148
|
+
Jsep.max_binop_len = Math.max(op_name.length, Jsep.max_binop_len);
|
|
149
|
+
Jsep.binary_ops[op_name] = precedence;
|
|
150
|
+
if (isRightAssociative) {
|
|
151
|
+
Jsep.right_associative.add(op_name);
|
|
152
|
+
} else {
|
|
153
|
+
Jsep.right_associative.delete(op_name);
|
|
154
|
+
}
|
|
155
|
+
return Jsep;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* @method addIdentifierChar
|
|
160
|
+
* @param {string} char The additional character to treat as a valid part of an identifier
|
|
161
|
+
* @returns {Jsep}
|
|
162
|
+
*/
|
|
163
|
+
static addIdentifierChar(char) {
|
|
164
|
+
Jsep.additional_identifier_chars.add(char);
|
|
165
|
+
return Jsep;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @method addLiteral
|
|
170
|
+
* @param {string} literal_name The name of the literal to add
|
|
171
|
+
* @param {*} literal_value The value of the literal
|
|
172
|
+
* @returns {Jsep}
|
|
173
|
+
*/
|
|
174
|
+
static addLiteral(literal_name, literal_value) {
|
|
175
|
+
Jsep.literals[literal_name] = literal_value;
|
|
176
|
+
return Jsep;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @method removeUnaryOp
|
|
181
|
+
* @param {string} op_name The name of the unary op to remove
|
|
182
|
+
* @returns {Jsep}
|
|
183
|
+
*/
|
|
184
|
+
static removeUnaryOp(op_name) {
|
|
185
|
+
delete Jsep.unary_ops[op_name];
|
|
186
|
+
if (op_name.length === Jsep.max_unop_len) {
|
|
187
|
+
Jsep.max_unop_len = Jsep.getMaxKeyLen(Jsep.unary_ops);
|
|
188
|
+
}
|
|
189
|
+
return Jsep;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* @method removeAllUnaryOps
|
|
194
|
+
* @returns {Jsep}
|
|
195
|
+
*/
|
|
196
|
+
static removeAllUnaryOps() {
|
|
197
|
+
Jsep.unary_ops = {};
|
|
198
|
+
Jsep.max_unop_len = 0;
|
|
199
|
+
return Jsep;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* @method removeIdentifierChar
|
|
204
|
+
* @param {string} char The additional character to stop treating as a valid part of an identifier
|
|
205
|
+
* @returns {Jsep}
|
|
206
|
+
*/
|
|
207
|
+
static removeIdentifierChar(char) {
|
|
208
|
+
Jsep.additional_identifier_chars.delete(char);
|
|
209
|
+
return Jsep;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* @method removeBinaryOp
|
|
214
|
+
* @param {string} op_name The name of the binary op to remove
|
|
215
|
+
* @returns {Jsep}
|
|
216
|
+
*/
|
|
217
|
+
static removeBinaryOp(op_name) {
|
|
218
|
+
delete Jsep.binary_ops[op_name];
|
|
219
|
+
if (op_name.length === Jsep.max_binop_len) {
|
|
220
|
+
Jsep.max_binop_len = Jsep.getMaxKeyLen(Jsep.binary_ops);
|
|
221
|
+
}
|
|
222
|
+
Jsep.right_associative.delete(op_name);
|
|
223
|
+
return Jsep;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* @method removeAllBinaryOps
|
|
228
|
+
* @returns {Jsep}
|
|
229
|
+
*/
|
|
230
|
+
static removeAllBinaryOps() {
|
|
231
|
+
Jsep.binary_ops = {};
|
|
232
|
+
Jsep.max_binop_len = 0;
|
|
233
|
+
return Jsep;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* @method removeLiteral
|
|
238
|
+
* @param {string} literal_name The name of the literal to remove
|
|
239
|
+
* @returns {Jsep}
|
|
240
|
+
*/
|
|
241
|
+
static removeLiteral(literal_name) {
|
|
242
|
+
delete Jsep.literals[literal_name];
|
|
243
|
+
return Jsep;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* @method removeAllLiterals
|
|
248
|
+
* @returns {Jsep}
|
|
249
|
+
*/
|
|
250
|
+
static removeAllLiterals() {
|
|
251
|
+
Jsep.literals = {};
|
|
252
|
+
return Jsep;
|
|
253
|
+
}
|
|
254
|
+
// ==================== END CONFIG ============================
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* @returns {string}
|
|
258
|
+
*/
|
|
259
|
+
get char() {
|
|
260
|
+
return this.expr.charAt(this.index);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* @returns {number}
|
|
265
|
+
*/
|
|
266
|
+
get code() {
|
|
267
|
+
return this.expr.charCodeAt(this.index);
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* @param {string} expr a string with the passed in express
|
|
271
|
+
* @returns Jsep
|
|
272
|
+
*/
|
|
273
|
+
constructor(expr) {
|
|
274
|
+
// `index` stores the character number we are currently at
|
|
275
|
+
// All of the gobbles below will modify `index` as we move along
|
|
276
|
+
this.expr = expr;
|
|
277
|
+
this.index = 0;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* static top-level parser
|
|
282
|
+
* @returns {jsep.Expression}
|
|
283
|
+
*/
|
|
284
|
+
static parse(expr) {
|
|
285
|
+
return new Jsep(expr).parse();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Get the longest key length of any object
|
|
290
|
+
* @param {object} obj
|
|
291
|
+
* @returns {number}
|
|
292
|
+
*/
|
|
293
|
+
static getMaxKeyLen(obj) {
|
|
294
|
+
return Math.max(0, ...Object.keys(obj).map(k => k.length));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* `ch` is a character code in the next three functions
|
|
299
|
+
* @param {number} ch
|
|
300
|
+
* @returns {boolean}
|
|
301
|
+
*/
|
|
302
|
+
static isDecimalDigit(ch) {
|
|
303
|
+
return ch >= 48 && ch <= 57; // 0...9
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Returns the precedence of a binary operator or `0` if it isn't a binary operator. Can be float.
|
|
308
|
+
* @param {string} op_val
|
|
309
|
+
* @returns {number}
|
|
310
|
+
*/
|
|
311
|
+
static binaryPrecedence(op_val) {
|
|
312
|
+
return Jsep.binary_ops[op_val] || 0;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Looks for start of identifier
|
|
317
|
+
* @param {number} ch
|
|
318
|
+
* @returns {boolean}
|
|
319
|
+
*/
|
|
320
|
+
static isIdentifierStart(ch) {
|
|
321
|
+
return ch >= 65 && ch <= 90 ||
|
|
322
|
+
// A...Z
|
|
323
|
+
ch >= 97 && ch <= 122 ||
|
|
324
|
+
// a...z
|
|
325
|
+
ch >= 128 && !Jsep.binary_ops[String.fromCharCode(ch)] ||
|
|
326
|
+
// any non-ASCII that is not an operator
|
|
327
|
+
Jsep.additional_identifier_chars.has(String.fromCharCode(ch)); // additional characters
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* @param {number} ch
|
|
332
|
+
* @returns {boolean}
|
|
333
|
+
*/
|
|
334
|
+
static isIdentifierPart(ch) {
|
|
335
|
+
return Jsep.isIdentifierStart(ch) || Jsep.isDecimalDigit(ch);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* throw error at index of the expression
|
|
340
|
+
* @param {string} message
|
|
341
|
+
* @throws
|
|
342
|
+
*/
|
|
343
|
+
throwError(message) {
|
|
344
|
+
const error = new Error(message + ' at character ' + this.index);
|
|
345
|
+
error.index = this.index;
|
|
346
|
+
error.description = message;
|
|
347
|
+
throw error;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Run a given hook
|
|
352
|
+
* @param {string} name
|
|
353
|
+
* @param {jsep.Expression|false} [node]
|
|
354
|
+
* @returns {?jsep.Expression}
|
|
355
|
+
*/
|
|
356
|
+
runHook(name, node) {
|
|
357
|
+
if (Jsep.hooks[name]) {
|
|
358
|
+
const env = {
|
|
359
|
+
context: this,
|
|
360
|
+
node
|
|
361
|
+
};
|
|
362
|
+
Jsep.hooks.run(name, env);
|
|
363
|
+
return env.node;
|
|
364
|
+
}
|
|
365
|
+
return node;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Runs a given hook until one returns a node
|
|
370
|
+
* @param {string} name
|
|
371
|
+
* @returns {?jsep.Expression}
|
|
372
|
+
*/
|
|
373
|
+
searchHook(name) {
|
|
374
|
+
if (Jsep.hooks[name]) {
|
|
375
|
+
const env = {
|
|
376
|
+
context: this
|
|
377
|
+
};
|
|
378
|
+
Jsep.hooks[name].find(function (callback) {
|
|
379
|
+
callback.call(env.context, env);
|
|
380
|
+
return env.node;
|
|
381
|
+
});
|
|
382
|
+
return env.node;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Push `index` up to the next non-space character
|
|
388
|
+
*/
|
|
389
|
+
gobbleSpaces() {
|
|
390
|
+
let ch = this.code;
|
|
391
|
+
// Whitespace
|
|
392
|
+
while (ch === Jsep.SPACE_CODE || ch === Jsep.TAB_CODE || ch === Jsep.LF_CODE || ch === Jsep.CR_CODE) {
|
|
393
|
+
ch = this.expr.charCodeAt(++this.index);
|
|
394
|
+
}
|
|
395
|
+
this.runHook('gobble-spaces');
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Top-level method to parse all expressions and returns compound or single node
|
|
400
|
+
* @returns {jsep.Expression}
|
|
401
|
+
*/
|
|
402
|
+
parse() {
|
|
403
|
+
this.runHook('before-all');
|
|
404
|
+
const nodes = this.gobbleExpressions();
|
|
405
|
+
|
|
406
|
+
// If there's only one expression just try returning the expression
|
|
407
|
+
const node = nodes.length === 1 ? nodes[0] : {
|
|
408
|
+
type: Jsep.COMPOUND,
|
|
409
|
+
body: nodes
|
|
410
|
+
};
|
|
411
|
+
return this.runHook('after-all', node);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* top-level parser (but can be reused within as well)
|
|
416
|
+
* @param {number} [untilICode]
|
|
417
|
+
* @returns {jsep.Expression[]}
|
|
418
|
+
*/
|
|
419
|
+
gobbleExpressions(untilICode) {
|
|
420
|
+
let nodes = [],
|
|
421
|
+
ch_i,
|
|
422
|
+
node;
|
|
423
|
+
while (this.index < this.expr.length) {
|
|
424
|
+
ch_i = this.code;
|
|
425
|
+
|
|
426
|
+
// Expressions can be separated by semicolons, commas, or just inferred without any
|
|
427
|
+
// separators
|
|
428
|
+
if (ch_i === Jsep.SEMCOL_CODE || ch_i === Jsep.COMMA_CODE) {
|
|
429
|
+
this.index++; // ignore separators
|
|
430
|
+
} else {
|
|
431
|
+
// Try to gobble each expression individually
|
|
432
|
+
if (node = this.gobbleExpression()) {
|
|
433
|
+
nodes.push(node);
|
|
434
|
+
// If we weren't able to find a binary expression and are out of room, then
|
|
435
|
+
// the expression passed in probably has too much
|
|
436
|
+
} else if (this.index < this.expr.length) {
|
|
437
|
+
if (ch_i === untilICode) {
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
this.throwError('Unexpected "' + this.char + '"');
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return nodes;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* The main parsing function.
|
|
449
|
+
* @returns {?jsep.Expression}
|
|
450
|
+
*/
|
|
451
|
+
gobbleExpression() {
|
|
452
|
+
const node = this.searchHook('gobble-expression') || this.gobbleBinaryExpression();
|
|
453
|
+
this.gobbleSpaces();
|
|
454
|
+
return this.runHook('after-expression', node);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Search for the operation portion of the string (e.g. `+`, `===`)
|
|
459
|
+
* Start by taking the longest possible binary operations (3 characters: `===`, `!==`, `>>>`)
|
|
460
|
+
* and move down from 3 to 2 to 1 character until a matching binary operation is found
|
|
461
|
+
* then, return that binary operation
|
|
462
|
+
* @returns {string|boolean}
|
|
463
|
+
*/
|
|
464
|
+
gobbleBinaryOp() {
|
|
465
|
+
this.gobbleSpaces();
|
|
466
|
+
let to_check = this.expr.substr(this.index, Jsep.max_binop_len);
|
|
467
|
+
let tc_len = to_check.length;
|
|
468
|
+
while (tc_len > 0) {
|
|
469
|
+
// Don't accept a binary op when it is an identifier.
|
|
470
|
+
// Binary ops that start with a identifier-valid character must be followed
|
|
471
|
+
// by a non identifier-part valid character
|
|
472
|
+
if (Jsep.binary_ops.hasOwnProperty(to_check) && (!Jsep.isIdentifierStart(this.code) || this.index + to_check.length < this.expr.length && !Jsep.isIdentifierPart(this.expr.charCodeAt(this.index + to_check.length)))) {
|
|
473
|
+
this.index += tc_len;
|
|
474
|
+
return to_check;
|
|
475
|
+
}
|
|
476
|
+
to_check = to_check.substr(0, --tc_len);
|
|
477
|
+
}
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* This function is responsible for gobbling an individual expression,
|
|
483
|
+
* e.g. `1`, `1+2`, `a+(b*2)-Math.sqrt(2)`
|
|
484
|
+
* @returns {?jsep.BinaryExpression}
|
|
485
|
+
*/
|
|
486
|
+
gobbleBinaryExpression() {
|
|
487
|
+
let node, biop, prec, stack, biop_info, left, right, i, cur_biop;
|
|
488
|
+
|
|
489
|
+
// First, try to get the leftmost thing
|
|
490
|
+
// Then, check to see if there's a binary operator operating on that leftmost thing
|
|
491
|
+
// Don't gobbleBinaryOp without a left-hand-side
|
|
492
|
+
left = this.gobbleToken();
|
|
493
|
+
if (!left) {
|
|
494
|
+
return left;
|
|
495
|
+
}
|
|
496
|
+
biop = this.gobbleBinaryOp();
|
|
497
|
+
|
|
498
|
+
// If there wasn't a binary operator, just return the leftmost node
|
|
499
|
+
if (!biop) {
|
|
500
|
+
return left;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Otherwise, we need to start a stack to properly place the binary operations in their
|
|
504
|
+
// precedence structure
|
|
505
|
+
biop_info = {
|
|
506
|
+
value: biop,
|
|
507
|
+
prec: Jsep.binaryPrecedence(biop),
|
|
508
|
+
right_a: Jsep.right_associative.has(biop)
|
|
509
|
+
};
|
|
510
|
+
right = this.gobbleToken();
|
|
511
|
+
if (!right) {
|
|
512
|
+
this.throwError("Expected expression after " + biop);
|
|
513
|
+
}
|
|
514
|
+
stack = [left, biop_info, right];
|
|
515
|
+
|
|
516
|
+
// Properly deal with precedence using [recursive descent](http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm)
|
|
517
|
+
while (biop = this.gobbleBinaryOp()) {
|
|
518
|
+
prec = Jsep.binaryPrecedence(biop);
|
|
519
|
+
if (prec === 0) {
|
|
520
|
+
this.index -= biop.length;
|
|
521
|
+
break;
|
|
522
|
+
}
|
|
523
|
+
biop_info = {
|
|
524
|
+
value: biop,
|
|
525
|
+
prec,
|
|
526
|
+
right_a: Jsep.right_associative.has(biop)
|
|
527
|
+
};
|
|
528
|
+
cur_biop = biop;
|
|
529
|
+
|
|
530
|
+
// Reduce: make a binary expression from the three topmost entries.
|
|
531
|
+
const comparePrev = prev => biop_info.right_a && prev.right_a ? prec > prev.prec : prec <= prev.prec;
|
|
532
|
+
while (stack.length > 2 && comparePrev(stack[stack.length - 2])) {
|
|
533
|
+
right = stack.pop();
|
|
534
|
+
biop = stack.pop().value;
|
|
535
|
+
left = stack.pop();
|
|
536
|
+
node = {
|
|
537
|
+
type: Jsep.BINARY_EXP,
|
|
538
|
+
operator: biop,
|
|
539
|
+
left,
|
|
540
|
+
right
|
|
541
|
+
};
|
|
542
|
+
stack.push(node);
|
|
543
|
+
}
|
|
544
|
+
node = this.gobbleToken();
|
|
545
|
+
if (!node) {
|
|
546
|
+
this.throwError("Expected expression after " + cur_biop);
|
|
547
|
+
}
|
|
548
|
+
stack.push(biop_info, node);
|
|
549
|
+
}
|
|
550
|
+
i = stack.length - 1;
|
|
551
|
+
node = stack[i];
|
|
552
|
+
while (i > 1) {
|
|
553
|
+
node = {
|
|
554
|
+
type: Jsep.BINARY_EXP,
|
|
555
|
+
operator: stack[i - 1].value,
|
|
556
|
+
left: stack[i - 2],
|
|
557
|
+
right: node
|
|
558
|
+
};
|
|
559
|
+
i -= 2;
|
|
560
|
+
}
|
|
561
|
+
return node;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* An individual part of a binary expression:
|
|
566
|
+
* e.g. `foo.bar(baz)`, `1`, `"abc"`, `(a % 2)` (because it's in parenthesis)
|
|
567
|
+
* @returns {boolean|jsep.Expression}
|
|
568
|
+
*/
|
|
569
|
+
gobbleToken() {
|
|
570
|
+
let ch, to_check, tc_len, node;
|
|
571
|
+
this.gobbleSpaces();
|
|
572
|
+
node = this.searchHook('gobble-token');
|
|
573
|
+
if (node) {
|
|
574
|
+
return this.runHook('after-token', node);
|
|
575
|
+
}
|
|
576
|
+
ch = this.code;
|
|
577
|
+
if (Jsep.isDecimalDigit(ch) || ch === Jsep.PERIOD_CODE) {
|
|
578
|
+
// Char code 46 is a dot `.` which can start off a numeric literal
|
|
579
|
+
return this.gobbleNumericLiteral();
|
|
580
|
+
}
|
|
581
|
+
if (ch === Jsep.SQUOTE_CODE || ch === Jsep.DQUOTE_CODE) {
|
|
582
|
+
// Single or double quotes
|
|
583
|
+
node = this.gobbleStringLiteral();
|
|
584
|
+
} else if (ch === Jsep.OBRACK_CODE) {
|
|
585
|
+
node = this.gobbleArray();
|
|
586
|
+
} else {
|
|
587
|
+
to_check = this.expr.substr(this.index, Jsep.max_unop_len);
|
|
588
|
+
tc_len = to_check.length;
|
|
589
|
+
while (tc_len > 0) {
|
|
590
|
+
// Don't accept an unary op when it is an identifier.
|
|
591
|
+
// Unary ops that start with a identifier-valid character must be followed
|
|
592
|
+
// by a non identifier-part valid character
|
|
593
|
+
if (Jsep.unary_ops.hasOwnProperty(to_check) && (!Jsep.isIdentifierStart(this.code) || this.index + to_check.length < this.expr.length && !Jsep.isIdentifierPart(this.expr.charCodeAt(this.index + to_check.length)))) {
|
|
594
|
+
this.index += tc_len;
|
|
595
|
+
const argument = this.gobbleToken();
|
|
596
|
+
if (!argument) {
|
|
597
|
+
this.throwError('missing unaryOp argument');
|
|
598
|
+
}
|
|
599
|
+
return this.runHook('after-token', {
|
|
600
|
+
type: Jsep.UNARY_EXP,
|
|
601
|
+
operator: to_check,
|
|
602
|
+
argument,
|
|
603
|
+
prefix: true
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
to_check = to_check.substr(0, --tc_len);
|
|
607
|
+
}
|
|
608
|
+
if (Jsep.isIdentifierStart(ch)) {
|
|
609
|
+
node = this.gobbleIdentifier();
|
|
610
|
+
if (Jsep.literals.hasOwnProperty(node.name)) {
|
|
611
|
+
node = {
|
|
612
|
+
type: Jsep.LITERAL,
|
|
613
|
+
value: Jsep.literals[node.name],
|
|
614
|
+
raw: node.name
|
|
615
|
+
};
|
|
616
|
+
} else if (node.name === Jsep.this_str) {
|
|
617
|
+
node = {
|
|
618
|
+
type: Jsep.THIS_EXP
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
} else if (ch === Jsep.OPAREN_CODE) {
|
|
622
|
+
// open parenthesis
|
|
623
|
+
node = this.gobbleGroup();
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
if (!node) {
|
|
627
|
+
return this.runHook('after-token', false);
|
|
628
|
+
}
|
|
629
|
+
node = this.gobbleTokenProperty(node);
|
|
630
|
+
return this.runHook('after-token', node);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Gobble properties of of identifiers/strings/arrays/groups.
|
|
635
|
+
* e.g. `foo`, `bar.baz`, `foo['bar'].baz`
|
|
636
|
+
* It also gobbles function calls:
|
|
637
|
+
* e.g. `Math.acos(obj.angle)`
|
|
638
|
+
* @param {jsep.Expression} node
|
|
639
|
+
* @returns {jsep.Expression}
|
|
640
|
+
*/
|
|
641
|
+
gobbleTokenProperty(node) {
|
|
642
|
+
this.gobbleSpaces();
|
|
643
|
+
let ch = this.code;
|
|
644
|
+
while (ch === Jsep.PERIOD_CODE || ch === Jsep.OBRACK_CODE || ch === Jsep.OPAREN_CODE || ch === Jsep.QUMARK_CODE) {
|
|
645
|
+
let optional;
|
|
646
|
+
if (ch === Jsep.QUMARK_CODE) {
|
|
647
|
+
if (this.expr.charCodeAt(this.index + 1) !== Jsep.PERIOD_CODE) {
|
|
648
|
+
break;
|
|
649
|
+
}
|
|
650
|
+
optional = true;
|
|
651
|
+
this.index += 2;
|
|
652
|
+
this.gobbleSpaces();
|
|
653
|
+
ch = this.code;
|
|
654
|
+
}
|
|
655
|
+
this.index++;
|
|
656
|
+
if (ch === Jsep.OBRACK_CODE) {
|
|
657
|
+
node = {
|
|
658
|
+
type: Jsep.MEMBER_EXP,
|
|
659
|
+
computed: true,
|
|
660
|
+
object: node,
|
|
661
|
+
property: this.gobbleExpression()
|
|
662
|
+
};
|
|
663
|
+
if (!node.property) {
|
|
664
|
+
this.throwError('Unexpected "' + this.char + '"');
|
|
665
|
+
}
|
|
666
|
+
this.gobbleSpaces();
|
|
667
|
+
ch = this.code;
|
|
668
|
+
if (ch !== Jsep.CBRACK_CODE) {
|
|
669
|
+
this.throwError('Unclosed [');
|
|
670
|
+
}
|
|
671
|
+
this.index++;
|
|
672
|
+
} else if (ch === Jsep.OPAREN_CODE) {
|
|
673
|
+
// A function call is being made; gobble all the arguments
|
|
674
|
+
node = {
|
|
675
|
+
type: Jsep.CALL_EXP,
|
|
676
|
+
'arguments': this.gobbleArguments(Jsep.CPAREN_CODE),
|
|
677
|
+
callee: node
|
|
678
|
+
};
|
|
679
|
+
} else if (ch === Jsep.PERIOD_CODE || optional) {
|
|
680
|
+
if (optional) {
|
|
681
|
+
this.index--;
|
|
682
|
+
}
|
|
683
|
+
this.gobbleSpaces();
|
|
684
|
+
node = {
|
|
685
|
+
type: Jsep.MEMBER_EXP,
|
|
686
|
+
computed: false,
|
|
687
|
+
object: node,
|
|
688
|
+
property: this.gobbleIdentifier()
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
if (optional) {
|
|
692
|
+
node.optional = true;
|
|
693
|
+
} // else leave undefined for compatibility with esprima
|
|
694
|
+
|
|
695
|
+
this.gobbleSpaces();
|
|
696
|
+
ch = this.code;
|
|
697
|
+
}
|
|
698
|
+
return node;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Parse simple numeric literals: `12`, `3.4`, `.5`. Do this by using a string to
|
|
703
|
+
* keep track of everything in the numeric literal and then calling `parseFloat` on that string
|
|
704
|
+
* @returns {jsep.Literal}
|
|
705
|
+
*/
|
|
706
|
+
gobbleNumericLiteral() {
|
|
707
|
+
let number = '',
|
|
708
|
+
ch,
|
|
709
|
+
chCode;
|
|
710
|
+
while (Jsep.isDecimalDigit(this.code)) {
|
|
711
|
+
number += this.expr.charAt(this.index++);
|
|
712
|
+
}
|
|
713
|
+
if (this.code === Jsep.PERIOD_CODE) {
|
|
714
|
+
// can start with a decimal marker
|
|
715
|
+
number += this.expr.charAt(this.index++);
|
|
716
|
+
while (Jsep.isDecimalDigit(this.code)) {
|
|
717
|
+
number += this.expr.charAt(this.index++);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
ch = this.char;
|
|
721
|
+
if (ch === 'e' || ch === 'E') {
|
|
722
|
+
// exponent marker
|
|
723
|
+
number += this.expr.charAt(this.index++);
|
|
724
|
+
ch = this.char;
|
|
725
|
+
if (ch === '+' || ch === '-') {
|
|
726
|
+
// exponent sign
|
|
727
|
+
number += this.expr.charAt(this.index++);
|
|
728
|
+
}
|
|
729
|
+
while (Jsep.isDecimalDigit(this.code)) {
|
|
730
|
+
// exponent itself
|
|
731
|
+
number += this.expr.charAt(this.index++);
|
|
732
|
+
}
|
|
733
|
+
if (!Jsep.isDecimalDigit(this.expr.charCodeAt(this.index - 1))) {
|
|
734
|
+
this.throwError('Expected exponent (' + number + this.char + ')');
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
chCode = this.code;
|
|
738
|
+
|
|
739
|
+
// Check to make sure this isn't a variable name that start with a number (123abc)
|
|
740
|
+
if (Jsep.isIdentifierStart(chCode)) {
|
|
741
|
+
this.throwError('Variable names cannot start with a number (' + number + this.char + ')');
|
|
742
|
+
} else if (chCode === Jsep.PERIOD_CODE || number.length === 1 && number.charCodeAt(0) === Jsep.PERIOD_CODE) {
|
|
743
|
+
this.throwError('Unexpected period');
|
|
744
|
+
}
|
|
745
|
+
return {
|
|
746
|
+
type: Jsep.LITERAL,
|
|
747
|
+
value: parseFloat(number),
|
|
748
|
+
raw: number
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Parses a string literal, staring with single or double quotes with basic support for escape codes
|
|
754
|
+
* e.g. `"hello world"`, `'this is\nJSEP'`
|
|
755
|
+
* @returns {jsep.Literal}
|
|
756
|
+
*/
|
|
757
|
+
gobbleStringLiteral() {
|
|
758
|
+
let str = '';
|
|
759
|
+
const startIndex = this.index;
|
|
760
|
+
const quote = this.expr.charAt(this.index++);
|
|
761
|
+
let closed = false;
|
|
762
|
+
while (this.index < this.expr.length) {
|
|
763
|
+
let ch = this.expr.charAt(this.index++);
|
|
764
|
+
if (ch === quote) {
|
|
765
|
+
closed = true;
|
|
766
|
+
break;
|
|
767
|
+
} else if (ch === '\\') {
|
|
768
|
+
// Check for all of the common escape codes
|
|
769
|
+
ch = this.expr.charAt(this.index++);
|
|
770
|
+
switch (ch) {
|
|
771
|
+
case 'n':
|
|
772
|
+
str += '\n';
|
|
773
|
+
break;
|
|
774
|
+
case 'r':
|
|
775
|
+
str += '\r';
|
|
776
|
+
break;
|
|
777
|
+
case 't':
|
|
778
|
+
str += '\t';
|
|
779
|
+
break;
|
|
780
|
+
case 'b':
|
|
781
|
+
str += '\b';
|
|
782
|
+
break;
|
|
783
|
+
case 'f':
|
|
784
|
+
str += '\f';
|
|
785
|
+
break;
|
|
786
|
+
case 'v':
|
|
787
|
+
str += '\x0B';
|
|
788
|
+
break;
|
|
789
|
+
default:
|
|
790
|
+
str += ch;
|
|
791
|
+
}
|
|
792
|
+
} else {
|
|
793
|
+
str += ch;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if (!closed) {
|
|
797
|
+
this.throwError('Unclosed quote after "' + str + '"');
|
|
798
|
+
}
|
|
799
|
+
return {
|
|
800
|
+
type: Jsep.LITERAL,
|
|
801
|
+
value: str,
|
|
802
|
+
raw: this.expr.substring(startIndex, this.index)
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* Gobbles only identifiers
|
|
808
|
+
* e.g.: `foo`, `_value`, `$x1`
|
|
809
|
+
* Also, this function checks if that identifier is a literal:
|
|
810
|
+
* (e.g. `true`, `false`, `null`) or `this`
|
|
811
|
+
* @returns {jsep.Identifier}
|
|
812
|
+
*/
|
|
813
|
+
gobbleIdentifier() {
|
|
814
|
+
let ch = this.code,
|
|
815
|
+
start = this.index;
|
|
816
|
+
if (Jsep.isIdentifierStart(ch)) {
|
|
817
|
+
this.index++;
|
|
818
|
+
} else {
|
|
819
|
+
this.throwError('Unexpected ' + this.char);
|
|
820
|
+
}
|
|
821
|
+
while (this.index < this.expr.length) {
|
|
822
|
+
ch = this.code;
|
|
823
|
+
if (Jsep.isIdentifierPart(ch)) {
|
|
824
|
+
this.index++;
|
|
825
|
+
} else {
|
|
826
|
+
break;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
return {
|
|
830
|
+
type: Jsep.IDENTIFIER,
|
|
831
|
+
name: this.expr.slice(start, this.index)
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* Gobbles a list of arguments within the context of a function call
|
|
837
|
+
* or array literal. This function also assumes that the opening character
|
|
838
|
+
* `(` or `[` has already been gobbled, and gobbles expressions and commas
|
|
839
|
+
* until the terminator character `)` or `]` is encountered.
|
|
840
|
+
* e.g. `foo(bar, baz)`, `my_func()`, or `[bar, baz]`
|
|
841
|
+
* @param {number} termination
|
|
842
|
+
* @returns {jsep.Expression[]}
|
|
843
|
+
*/
|
|
844
|
+
gobbleArguments(termination) {
|
|
845
|
+
const args = [];
|
|
846
|
+
let closed = false;
|
|
847
|
+
let separator_count = 0;
|
|
848
|
+
while (this.index < this.expr.length) {
|
|
849
|
+
this.gobbleSpaces();
|
|
850
|
+
let ch_i = this.code;
|
|
851
|
+
if (ch_i === termination) {
|
|
852
|
+
// done parsing
|
|
853
|
+
closed = true;
|
|
854
|
+
this.index++;
|
|
855
|
+
if (termination === Jsep.CPAREN_CODE && separator_count && separator_count >= args.length) {
|
|
856
|
+
this.throwError('Unexpected token ' + String.fromCharCode(termination));
|
|
857
|
+
}
|
|
858
|
+
break;
|
|
859
|
+
} else if (ch_i === Jsep.COMMA_CODE) {
|
|
860
|
+
// between expressions
|
|
861
|
+
this.index++;
|
|
862
|
+
separator_count++;
|
|
863
|
+
if (separator_count !== args.length) {
|
|
864
|
+
// missing argument
|
|
865
|
+
if (termination === Jsep.CPAREN_CODE) {
|
|
866
|
+
this.throwError('Unexpected token ,');
|
|
867
|
+
} else if (termination === Jsep.CBRACK_CODE) {
|
|
868
|
+
for (let arg = args.length; arg < separator_count; arg++) {
|
|
869
|
+
args.push(null);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
} else if (args.length !== separator_count && separator_count !== 0) {
|
|
874
|
+
// NOTE: `&& separator_count !== 0` allows for either all commas, or all spaces as arguments
|
|
875
|
+
this.throwError('Expected comma');
|
|
876
|
+
} else {
|
|
877
|
+
const node = this.gobbleExpression();
|
|
878
|
+
if (!node || node.type === Jsep.COMPOUND) {
|
|
879
|
+
this.throwError('Expected comma');
|
|
880
|
+
}
|
|
881
|
+
args.push(node);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
if (!closed) {
|
|
885
|
+
this.throwError('Expected ' + String.fromCharCode(termination));
|
|
886
|
+
}
|
|
887
|
+
return args;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* Responsible for parsing a group of things within parentheses `()`
|
|
892
|
+
* that have no identifier in front (so not a function call)
|
|
893
|
+
* This function assumes that it needs to gobble the opening parenthesis
|
|
894
|
+
* and then tries to gobble everything within that parenthesis, assuming
|
|
895
|
+
* that the next thing it should see is the close parenthesis. If not,
|
|
896
|
+
* then the expression probably doesn't have a `)`
|
|
897
|
+
* @returns {boolean|jsep.Expression}
|
|
898
|
+
*/
|
|
899
|
+
gobbleGroup() {
|
|
900
|
+
this.index++;
|
|
901
|
+
let nodes = this.gobbleExpressions(Jsep.CPAREN_CODE);
|
|
902
|
+
if (this.code === Jsep.CPAREN_CODE) {
|
|
903
|
+
this.index++;
|
|
904
|
+
if (nodes.length === 1) {
|
|
905
|
+
return nodes[0];
|
|
906
|
+
} else if (!nodes.length) {
|
|
907
|
+
return false;
|
|
908
|
+
} else {
|
|
909
|
+
return {
|
|
910
|
+
type: Jsep.SEQUENCE_EXP,
|
|
911
|
+
expressions: nodes
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
} else {
|
|
915
|
+
this.throwError('Unclosed (');
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Responsible for parsing Array literals `[1, 2, 3]`
|
|
921
|
+
* This function assumes that it needs to gobble the opening bracket
|
|
922
|
+
* and then tries to gobble the expressions as arguments.
|
|
923
|
+
* @returns {jsep.ArrayExpression}
|
|
924
|
+
*/
|
|
925
|
+
gobbleArray() {
|
|
926
|
+
this.index++;
|
|
927
|
+
return {
|
|
928
|
+
type: Jsep.ARRAY_EXP,
|
|
929
|
+
elements: this.gobbleArguments(Jsep.CBRACK_CODE)
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// Static fields:
|
|
935
|
+
const hooks = new Hooks();
|
|
936
|
+
Object.assign(Jsep, {
|
|
937
|
+
hooks,
|
|
938
|
+
plugins: new Plugins(Jsep),
|
|
939
|
+
// Node Types
|
|
940
|
+
// ----------
|
|
941
|
+
// This is the full set of types that any JSEP node can be.
|
|
942
|
+
// Store them here to save space when minified
|
|
943
|
+
COMPOUND: 'Compound',
|
|
944
|
+
SEQUENCE_EXP: 'SequenceExpression',
|
|
945
|
+
IDENTIFIER: 'Identifier',
|
|
946
|
+
MEMBER_EXP: 'MemberExpression',
|
|
947
|
+
LITERAL: 'Literal',
|
|
948
|
+
THIS_EXP: 'ThisExpression',
|
|
949
|
+
CALL_EXP: 'CallExpression',
|
|
950
|
+
UNARY_EXP: 'UnaryExpression',
|
|
951
|
+
BINARY_EXP: 'BinaryExpression',
|
|
952
|
+
ARRAY_EXP: 'ArrayExpression',
|
|
953
|
+
TAB_CODE: 9,
|
|
954
|
+
LF_CODE: 10,
|
|
955
|
+
CR_CODE: 13,
|
|
956
|
+
SPACE_CODE: 32,
|
|
957
|
+
PERIOD_CODE: 46,
|
|
958
|
+
// '.'
|
|
959
|
+
COMMA_CODE: 44,
|
|
960
|
+
// ','
|
|
961
|
+
SQUOTE_CODE: 39,
|
|
962
|
+
// single quote
|
|
963
|
+
DQUOTE_CODE: 34,
|
|
964
|
+
// double quotes
|
|
965
|
+
OPAREN_CODE: 40,
|
|
966
|
+
// (
|
|
967
|
+
CPAREN_CODE: 41,
|
|
968
|
+
// )
|
|
969
|
+
OBRACK_CODE: 91,
|
|
970
|
+
// [
|
|
971
|
+
CBRACK_CODE: 93,
|
|
972
|
+
// ]
|
|
973
|
+
QUMARK_CODE: 63,
|
|
974
|
+
// ?
|
|
975
|
+
SEMCOL_CODE: 59,
|
|
976
|
+
// ;
|
|
977
|
+
COLON_CODE: 58,
|
|
978
|
+
// :
|
|
979
|
+
|
|
980
|
+
// Operations
|
|
981
|
+
// ----------
|
|
982
|
+
// Use a quickly-accessible map to store all of the unary operators
|
|
983
|
+
// Values are set to `1` (it really doesn't matter)
|
|
984
|
+
unary_ops: {
|
|
985
|
+
'-': 1,
|
|
986
|
+
'!': 1,
|
|
987
|
+
'~': 1,
|
|
988
|
+
'+': 1
|
|
989
|
+
},
|
|
990
|
+
// Also use a map for the binary operations but set their values to their
|
|
991
|
+
// binary precedence for quick reference (higher number = higher precedence)
|
|
992
|
+
// see [Order of operations](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence)
|
|
993
|
+
binary_ops: {
|
|
994
|
+
'||': 1,
|
|
995
|
+
'??': 1,
|
|
996
|
+
'&&': 2,
|
|
997
|
+
'|': 3,
|
|
998
|
+
'^': 4,
|
|
999
|
+
'&': 5,
|
|
1000
|
+
'==': 6,
|
|
1001
|
+
'!=': 6,
|
|
1002
|
+
'===': 6,
|
|
1003
|
+
'!==': 6,
|
|
1004
|
+
'<': 7,
|
|
1005
|
+
'>': 7,
|
|
1006
|
+
'<=': 7,
|
|
1007
|
+
'>=': 7,
|
|
1008
|
+
'<<': 8,
|
|
1009
|
+
'>>': 8,
|
|
1010
|
+
'>>>': 8,
|
|
1011
|
+
'+': 9,
|
|
1012
|
+
'-': 9,
|
|
1013
|
+
'*': 10,
|
|
1014
|
+
'/': 10,
|
|
1015
|
+
'%': 10,
|
|
1016
|
+
'**': 11
|
|
1017
|
+
},
|
|
1018
|
+
// sets specific binary_ops as right-associative
|
|
1019
|
+
right_associative: new Set(['**']),
|
|
1020
|
+
// Additional valid identifier chars, apart from a-z, A-Z and 0-9 (except on the starting char)
|
|
1021
|
+
additional_identifier_chars: new Set(['$', '_']),
|
|
1022
|
+
// Literals
|
|
1023
|
+
// ----------
|
|
1024
|
+
// Store the values to return for the various literals we may encounter
|
|
1025
|
+
literals: {
|
|
1026
|
+
'true': true,
|
|
1027
|
+
'false': false,
|
|
1028
|
+
'null': null
|
|
1029
|
+
},
|
|
1030
|
+
// Except for `this`, which is special. This could be changed to something like `'self'` as well
|
|
1031
|
+
this_str: 'this'
|
|
1032
|
+
});
|
|
1033
|
+
Jsep.max_unop_len = Jsep.getMaxKeyLen(Jsep.unary_ops);
|
|
1034
|
+
Jsep.max_binop_len = Jsep.getMaxKeyLen(Jsep.binary_ops);
|
|
1035
|
+
|
|
1036
|
+
// Backward Compatibility:
|
|
1037
|
+
const jsep = expr => new Jsep(expr).parse();
|
|
1038
|
+
const stdClassProps = Object.getOwnPropertyNames(class Test {});
|
|
1039
|
+
Object.getOwnPropertyNames(Jsep).filter(prop => !stdClassProps.includes(prop) && jsep[prop] === undefined).forEach(m => {
|
|
1040
|
+
jsep[m] = Jsep[m];
|
|
1041
|
+
});
|
|
1042
|
+
jsep.Jsep = Jsep; // allows for const { Jsep } = require('jsep');
|
|
1043
|
+
|
|
1044
|
+
const CONDITIONAL_EXP = 'ConditionalExpression';
|
|
1045
|
+
var ternary = {
|
|
1046
|
+
name: 'ternary',
|
|
1047
|
+
init(jsep) {
|
|
1048
|
+
// Ternary expression: test ? consequent : alternate
|
|
1049
|
+
jsep.hooks.add('after-expression', function gobbleTernary(env) {
|
|
1050
|
+
if (env.node && this.code === jsep.QUMARK_CODE) {
|
|
1051
|
+
this.index++;
|
|
1052
|
+
const test = env.node;
|
|
1053
|
+
const consequent = this.gobbleExpression();
|
|
1054
|
+
if (!consequent) {
|
|
1055
|
+
this.throwError('Expected expression');
|
|
1056
|
+
}
|
|
1057
|
+
this.gobbleSpaces();
|
|
1058
|
+
if (this.code === jsep.COLON_CODE) {
|
|
1059
|
+
this.index++;
|
|
1060
|
+
const alternate = this.gobbleExpression();
|
|
1061
|
+
if (!alternate) {
|
|
1062
|
+
this.throwError('Expected expression');
|
|
1063
|
+
}
|
|
1064
|
+
env.node = {
|
|
1065
|
+
type: CONDITIONAL_EXP,
|
|
1066
|
+
test,
|
|
1067
|
+
consequent,
|
|
1068
|
+
alternate
|
|
1069
|
+
};
|
|
1070
|
+
|
|
1071
|
+
// check for operators of higher priority than ternary (i.e. assignment)
|
|
1072
|
+
// jsep sets || at 1, and assignment at 0.9, and conditional should be between them
|
|
1073
|
+
if (test.operator && jsep.binary_ops[test.operator] <= 0.9) {
|
|
1074
|
+
let newTest = test;
|
|
1075
|
+
while (newTest.right.operator && jsep.binary_ops[newTest.right.operator] <= 0.9) {
|
|
1076
|
+
newTest = newTest.right;
|
|
1077
|
+
}
|
|
1078
|
+
env.node.test = newTest.right;
|
|
1079
|
+
newTest.right = env.node;
|
|
1080
|
+
env.node = test;
|
|
1081
|
+
}
|
|
1082
|
+
} else {
|
|
1083
|
+
this.throwError('Expected :');
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
|
|
1090
|
+
// Add default plugins:
|
|
1091
|
+
|
|
1092
|
+
jsep.plugins.register(ternary);
|
|
1093
|
+
|
|
1094
|
+
const FSLASH_CODE = 47; // '/'
|
|
1095
|
+
const BSLASH_CODE = 92; // '\\'
|
|
1096
|
+
|
|
1097
|
+
var index = {
|
|
1098
|
+
name: 'regex',
|
|
1099
|
+
init(jsep) {
|
|
1100
|
+
// Regex literal: /abc123/ig
|
|
1101
|
+
jsep.hooks.add('gobble-token', function gobbleRegexLiteral(env) {
|
|
1102
|
+
if (this.code === FSLASH_CODE) {
|
|
1103
|
+
const patternIndex = ++this.index;
|
|
1104
|
+
let inCharSet = false;
|
|
1105
|
+
while (this.index < this.expr.length) {
|
|
1106
|
+
if (this.code === FSLASH_CODE && !inCharSet) {
|
|
1107
|
+
const pattern = this.expr.slice(patternIndex, this.index);
|
|
1108
|
+
let flags = '';
|
|
1109
|
+
while (++this.index < this.expr.length) {
|
|
1110
|
+
const code = this.code;
|
|
1111
|
+
if (code >= 97 && code <= 122 // a...z
|
|
1112
|
+
|| code >= 65 && code <= 90 // A...Z
|
|
1113
|
+
|| code >= 48 && code <= 57) {
|
|
1114
|
+
// 0-9
|
|
1115
|
+
flags += this.char;
|
|
1116
|
+
} else {
|
|
1117
|
+
break;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
let value;
|
|
1121
|
+
try {
|
|
1122
|
+
value = new RegExp(pattern, flags);
|
|
1123
|
+
} catch (e) {
|
|
1124
|
+
this.throwError(e.message);
|
|
1125
|
+
}
|
|
1126
|
+
env.node = {
|
|
1127
|
+
type: jsep.LITERAL,
|
|
1128
|
+
value,
|
|
1129
|
+
raw: this.expr.slice(patternIndex - 1, this.index)
|
|
1130
|
+
};
|
|
1131
|
+
|
|
1132
|
+
// allow . [] and () after regex: /regex/.test(a)
|
|
1133
|
+
env.node = this.gobbleTokenProperty(env.node);
|
|
1134
|
+
return env.node;
|
|
1135
|
+
}
|
|
1136
|
+
if (this.code === jsep.OBRACK_CODE) {
|
|
1137
|
+
inCharSet = true;
|
|
1138
|
+
} else if (inCharSet && this.code === jsep.CBRACK_CODE) {
|
|
1139
|
+
inCharSet = false;
|
|
1140
|
+
}
|
|
1141
|
+
this.index += this.code === BSLASH_CODE ? 2 : 1;
|
|
1142
|
+
}
|
|
1143
|
+
this.throwError('Unclosed Regex');
|
|
1144
|
+
}
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
|
|
1149
|
+
const PLUS_CODE = 43; // +
|
|
1150
|
+
const MINUS_CODE = 45; // -
|
|
1151
|
+
|
|
1152
|
+
const plugin = {
|
|
1153
|
+
name: 'assignment',
|
|
1154
|
+
assignmentOperators: new Set(['=', '*=', '**=', '/=', '%=', '+=', '-=', '<<=', '>>=', '>>>=', '&=', '^=', '|=', '||=', '&&=', '??=']),
|
|
1155
|
+
updateOperators: [PLUS_CODE, MINUS_CODE],
|
|
1156
|
+
assignmentPrecedence: 0.9,
|
|
1157
|
+
init(jsep) {
|
|
1158
|
+
const updateNodeTypes = [jsep.IDENTIFIER, jsep.MEMBER_EXP];
|
|
1159
|
+
plugin.assignmentOperators.forEach(op => jsep.addBinaryOp(op, plugin.assignmentPrecedence, true));
|
|
1160
|
+
jsep.hooks.add('gobble-token', function gobbleUpdatePrefix(env) {
|
|
1161
|
+
const code = this.code;
|
|
1162
|
+
if (plugin.updateOperators.some(c => c === code && c === this.expr.charCodeAt(this.index + 1))) {
|
|
1163
|
+
this.index += 2;
|
|
1164
|
+
env.node = {
|
|
1165
|
+
type: 'UpdateExpression',
|
|
1166
|
+
operator: code === PLUS_CODE ? '++' : '--',
|
|
1167
|
+
argument: this.gobbleTokenProperty(this.gobbleIdentifier()),
|
|
1168
|
+
prefix: true
|
|
1169
|
+
};
|
|
1170
|
+
if (!env.node.argument || !updateNodeTypes.includes(env.node.argument.type)) {
|
|
1171
|
+
this.throwError(`Unexpected ${env.node.operator}`);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
});
|
|
1175
|
+
jsep.hooks.add('after-token', function gobbleUpdatePostfix(env) {
|
|
1176
|
+
if (env.node) {
|
|
1177
|
+
const code = this.code;
|
|
1178
|
+
if (plugin.updateOperators.some(c => c === code && c === this.expr.charCodeAt(this.index + 1))) {
|
|
1179
|
+
if (!updateNodeTypes.includes(env.node.type)) {
|
|
1180
|
+
this.throwError(`Unexpected ${env.node.operator}`);
|
|
1181
|
+
}
|
|
1182
|
+
this.index += 2;
|
|
1183
|
+
env.node = {
|
|
1184
|
+
type: 'UpdateExpression',
|
|
1185
|
+
operator: code === PLUS_CODE ? '++' : '--',
|
|
1186
|
+
argument: env.node,
|
|
1187
|
+
prefix: false
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
});
|
|
1192
|
+
jsep.hooks.add('after-expression', function gobbleAssignment(env) {
|
|
1193
|
+
if (env.node) {
|
|
1194
|
+
// Note: Binaries can be chained in a single expression to respect
|
|
1195
|
+
// operator precedence (i.e. a = b = 1 + 2 + 3)
|
|
1196
|
+
// Update all binary assignment nodes in the tree
|
|
1197
|
+
updateBinariesToAssignments(env.node);
|
|
1198
|
+
}
|
|
1199
|
+
});
|
|
1200
|
+
function updateBinariesToAssignments(node) {
|
|
1201
|
+
if (plugin.assignmentOperators.has(node.operator)) {
|
|
1202
|
+
node.type = 'AssignmentExpression';
|
|
1203
|
+
updateBinariesToAssignments(node.left);
|
|
1204
|
+
updateBinariesToAssignments(node.right);
|
|
1205
|
+
} else if (!node.operator) {
|
|
1206
|
+
Object.values(node).forEach(val => {
|
|
1207
|
+
if (val && typeof val === 'object') {
|
|
1208
|
+
updateBinariesToAssignments(val);
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
|
|
1216
|
+
/* eslint-disable no-bitwise -- Convenient */
|
|
1217
|
+
|
|
1218
|
+
// register plugins
|
|
1219
|
+
jsep.plugins.register(index, plugin);
|
|
1220
|
+
jsep.addUnaryOp('typeof');
|
|
1221
|
+
jsep.addUnaryOp('void');
|
|
1222
|
+
jsep.addLiteral('null', null);
|
|
1223
|
+
jsep.addLiteral('undefined', undefined);
|
|
17
1224
|
|
|
18
1225
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
|
19
1226
|
|
|
@@ -2485,11 +3692,11 @@ function requireIdentifiers () {
|
|
|
2485
3692
|
return identifiers;
|
|
2486
3693
|
}
|
|
2487
3694
|
|
|
2488
|
-
var semver$
|
|
3695
|
+
var semver$2;
|
|
2489
3696
|
var hasRequiredSemver$1;
|
|
2490
3697
|
|
|
2491
3698
|
function requireSemver$1 () {
|
|
2492
|
-
if (hasRequiredSemver$1) return semver$
|
|
3699
|
+
if (hasRequiredSemver$1) return semver$2;
|
|
2493
3700
|
hasRequiredSemver$1 = 1;
|
|
2494
3701
|
|
|
2495
3702
|
const debug = requireDebug();
|
|
@@ -2822,8 +4029,8 @@ function requireSemver$1 () {
|
|
|
2822
4029
|
}
|
|
2823
4030
|
}
|
|
2824
4031
|
|
|
2825
|
-
semver$
|
|
2826
|
-
return semver$
|
|
4032
|
+
semver$2 = SemVer;
|
|
4033
|
+
return semver$2;
|
|
2827
4034
|
}
|
|
2828
4035
|
|
|
2829
4036
|
var parse_1;
|
|
@@ -4744,11 +5951,11 @@ function requireSubset () {
|
|
|
4744
5951
|
return subset_1;
|
|
4745
5952
|
}
|
|
4746
5953
|
|
|
4747
|
-
var semver;
|
|
5954
|
+
var semver$1;
|
|
4748
5955
|
var hasRequiredSemver;
|
|
4749
5956
|
|
|
4750
5957
|
function requireSemver () {
|
|
4751
|
-
if (hasRequiredSemver) return semver;
|
|
5958
|
+
if (hasRequiredSemver) return semver$1;
|
|
4752
5959
|
hasRequiredSemver = 1;
|
|
4753
5960
|
|
|
4754
5961
|
// just pre-load all the stuff that index.js lazily exports
|
|
@@ -4793,7 +6000,7 @@ function requireSemver () {
|
|
|
4793
6000
|
const intersects = requireIntersects();
|
|
4794
6001
|
const simplifyRange = requireSimplify();
|
|
4795
6002
|
const subset = requireSubset();
|
|
4796
|
-
semver = {
|
|
6003
|
+
semver$1 = {
|
|
4797
6004
|
parse,
|
|
4798
6005
|
valid,
|
|
4799
6006
|
clean,
|
|
@@ -4840,10 +6047,11 @@ function requireSemver () {
|
|
|
4840
6047
|
compareIdentifiers: identifiers.compareIdentifiers,
|
|
4841
6048
|
rcompareIdentifiers: identifiers.rcompareIdentifiers,
|
|
4842
6049
|
};
|
|
4843
|
-
return semver;
|
|
6050
|
+
return semver$1;
|
|
4844
6051
|
}
|
|
4845
6052
|
|
|
4846
6053
|
var semverExports = requireSemver();
|
|
6054
|
+
var semver = /*@__PURE__*/getDefaultExportFromCjs(semverExports);
|
|
4847
6055
|
|
|
4848
6056
|
function commonjsRequire(path) {
|
|
4849
6057
|
throw new Error('Could not dynamically require "' + path + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
|
|
@@ -17217,6 +18425,142 @@ ZodPromise.create;
|
|
|
17217
18425
|
ZodOptional.create;
|
|
17218
18426
|
ZodNullable.create;
|
|
17219
18427
|
|
|
18428
|
+
/**
|
|
18429
|
+
* Directory and file path conventions for the Jeeves platform.
|
|
18430
|
+
*/
|
|
18431
|
+
/** Core config directory name within the config root. */
|
|
18432
|
+
const CORE_CONFIG_DIR = 'jeeves-core';
|
|
18433
|
+
/** Prefix for component config directories: `jeeves-{name}`. */
|
|
18434
|
+
const COMPONENT_CONFIG_PREFIX = 'jeeves-';
|
|
18435
|
+
/** Default workspace file names. */
|
|
18436
|
+
const WORKSPACE_FILES = {
|
|
18437
|
+
/** TOOLS.md — live platform state and component sections. */
|
|
18438
|
+
tools: 'TOOLS.md',
|
|
18439
|
+
/** SOUL.md — professional discipline and behavioral foundations. */
|
|
18440
|
+
soul: 'SOUL.md',
|
|
18441
|
+
/** AGENTS.md — operational protocols and memory architecture. */
|
|
18442
|
+
agents: 'AGENTS.md',
|
|
18443
|
+
};
|
|
18444
|
+
/** Templates directory name within core config. */
|
|
18445
|
+
const TEMPLATES_DIR = 'templates';
|
|
18446
|
+
/** Registry cache file name. */
|
|
18447
|
+
const REGISTRY_CACHE_FILE = 'registry-cache.json';
|
|
18448
|
+
/** Core config file name. */
|
|
18449
|
+
const CONFIG_FILE = 'config.json';
|
|
18450
|
+
/** Component versions state file name. */
|
|
18451
|
+
const COMPONENT_VERSIONS_FILE = 'component-versions.json';
|
|
18452
|
+
|
|
18453
|
+
/**
|
|
18454
|
+
* Shared file I/O helpers for managed section operations.
|
|
18455
|
+
*
|
|
18456
|
+
* @remarks
|
|
18457
|
+
* Extracts the atomic write pattern and file-level locking into
|
|
18458
|
+
* reusable utilities, eliminating duplication between
|
|
18459
|
+
* `updateManagedSection` and `removeManagedSection`.
|
|
18460
|
+
*/
|
|
18461
|
+
/** Stale lock threshold in ms (2 minutes). */
|
|
18462
|
+
const STALE_LOCK_MS = 120_000;
|
|
18463
|
+
/** Default core version when none provided. */
|
|
18464
|
+
const DEFAULT_CORE_VERSION = '0.0.0';
|
|
18465
|
+
/** Lock retry options. */
|
|
18466
|
+
const LOCK_RETRIES = { retries: 5, minTimeout: 100, maxTimeout: 1000 };
|
|
18467
|
+
/**
|
|
18468
|
+
* Write content to a file atomically via a temp file + rename.
|
|
18469
|
+
*
|
|
18470
|
+
* @param filePath - Absolute path to the target file.
|
|
18471
|
+
* @param content - Content to write.
|
|
18472
|
+
*/
|
|
18473
|
+
function atomicWrite(filePath, content) {
|
|
18474
|
+
const dir = dirname(filePath);
|
|
18475
|
+
const tempPath = join(dir, `.${String(Date.now())}.tmp`);
|
|
18476
|
+
writeFileSync(tempPath, content, 'utf-8');
|
|
18477
|
+
renameSync(tempPath, filePath);
|
|
18478
|
+
}
|
|
18479
|
+
/**
|
|
18480
|
+
* Execute a callback while holding a file lock.
|
|
18481
|
+
*
|
|
18482
|
+
* @remarks
|
|
18483
|
+
* Acquires a lock on the file, executes the callback, and releases
|
|
18484
|
+
* the lock in a finally block. The lock uses a 2-minute stale threshold
|
|
18485
|
+
* and retries up to 5 times.
|
|
18486
|
+
*
|
|
18487
|
+
* @param filePath - Absolute path to the file to lock.
|
|
18488
|
+
* @param fn - Async callback to execute while holding the lock.
|
|
18489
|
+
*/
|
|
18490
|
+
async function withFileLock(filePath, fn) {
|
|
18491
|
+
let release;
|
|
18492
|
+
try {
|
|
18493
|
+
release = await properLockfileExports.lock(filePath, {
|
|
18494
|
+
stale: STALE_LOCK_MS,
|
|
18495
|
+
retries: LOCK_RETRIES,
|
|
18496
|
+
});
|
|
18497
|
+
await fn();
|
|
18498
|
+
}
|
|
18499
|
+
finally {
|
|
18500
|
+
if (release) {
|
|
18501
|
+
try {
|
|
18502
|
+
await release();
|
|
18503
|
+
}
|
|
18504
|
+
catch {
|
|
18505
|
+
// Lock already released or file deleted — safe to ignore
|
|
18506
|
+
}
|
|
18507
|
+
}
|
|
18508
|
+
}
|
|
18509
|
+
}
|
|
18510
|
+
|
|
18511
|
+
/**
|
|
18512
|
+
* Shared component version state file management.
|
|
18513
|
+
*
|
|
18514
|
+
* @remarks
|
|
18515
|
+
* Each `ComponentWriter` cycle writes its component's entry to
|
|
18516
|
+
* `{coreConfigDir}/component-versions.json`. The Platform Handlebars
|
|
18517
|
+
* template reads this file to populate ALL rows in the service health
|
|
18518
|
+
* table, not just the calling component's.
|
|
18519
|
+
*/
|
|
18520
|
+
/**
|
|
18521
|
+
* Read the component versions state file.
|
|
18522
|
+
*
|
|
18523
|
+
* @param coreConfigDir - Path to the core config directory.
|
|
18524
|
+
* @returns The parsed state, or an empty object if the file doesn't exist.
|
|
18525
|
+
*/
|
|
18526
|
+
function readComponentVersions(coreConfigDir) {
|
|
18527
|
+
const filePath = join(coreConfigDir, COMPONENT_VERSIONS_FILE);
|
|
18528
|
+
if (!existsSync(filePath))
|
|
18529
|
+
return {};
|
|
18530
|
+
try {
|
|
18531
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
18532
|
+
return JSON.parse(raw);
|
|
18533
|
+
}
|
|
18534
|
+
catch {
|
|
18535
|
+
return {};
|
|
18536
|
+
}
|
|
18537
|
+
}
|
|
18538
|
+
/**
|
|
18539
|
+
* Write a component's version entry to the shared state file.
|
|
18540
|
+
*
|
|
18541
|
+
* @remarks
|
|
18542
|
+
* Reads the existing file, merges the new entry, and writes atomically.
|
|
18543
|
+
*
|
|
18544
|
+
* @param coreConfigDir - Path to the core config directory.
|
|
18545
|
+
* @param options - Component version data to write.
|
|
18546
|
+
*/
|
|
18547
|
+
function writeComponentVersion(coreConfigDir, options) {
|
|
18548
|
+
const existing = readComponentVersions(coreConfigDir);
|
|
18549
|
+
existing[options.componentName] = {
|
|
18550
|
+
serviceVersion: options.serviceVersion,
|
|
18551
|
+
pluginVersion: options.pluginVersion,
|
|
18552
|
+
servicePackage: options.servicePackage,
|
|
18553
|
+
pluginPackage: options.pluginPackage,
|
|
18554
|
+
updatedAt: new Date().toISOString(),
|
|
18555
|
+
};
|
|
18556
|
+
const filePath = join(coreConfigDir, COMPONENT_VERSIONS_FILE);
|
|
18557
|
+
const dir = dirname(filePath);
|
|
18558
|
+
if (!existsSync(dir)) {
|
|
18559
|
+
mkdirSync(dir, { recursive: true });
|
|
18560
|
+
}
|
|
18561
|
+
atomicWrite(filePath, JSON.stringify(existing, null, 2) + '\n');
|
|
18562
|
+
}
|
|
18563
|
+
|
|
17220
18564
|
/**
|
|
17221
18565
|
* Comment markers for managed content blocks.
|
|
17222
18566
|
*
|
|
@@ -17266,29 +18610,6 @@ const STALENESS_THRESHOLD_MS = 5 * 60 * 1000;
|
|
|
17266
18610
|
/** Warning text prepended inside managed block when cleanup is needed. */
|
|
17267
18611
|
const CLEANUP_FLAG = '> ⚠️ CLEANUP NEEDED: Orphaned Jeeves content may exist below this managed section. Review everything after the END marker and remove any content that duplicates what appears above.';
|
|
17268
18612
|
|
|
17269
|
-
/**
|
|
17270
|
-
* Directory and file path conventions for the Jeeves platform.
|
|
17271
|
-
*/
|
|
17272
|
-
/** Core config directory name within the config root. */
|
|
17273
|
-
const CORE_CONFIG_DIR = 'jeeves-core';
|
|
17274
|
-
/** Prefix for component config directories: `jeeves-{name}`. */
|
|
17275
|
-
const COMPONENT_CONFIG_PREFIX = 'jeeves-';
|
|
17276
|
-
/** Default workspace file names. */
|
|
17277
|
-
const WORKSPACE_FILES = {
|
|
17278
|
-
/** TOOLS.md — live platform state and component sections. */
|
|
17279
|
-
tools: 'TOOLS.md',
|
|
17280
|
-
/** SOUL.md — professional discipline and behavioral foundations. */
|
|
17281
|
-
soul: 'SOUL.md',
|
|
17282
|
-
/** AGENTS.md — operational protocols and memory architecture. */
|
|
17283
|
-
agents: 'AGENTS.md',
|
|
17284
|
-
};
|
|
17285
|
-
/** Templates directory name within core config. */
|
|
17286
|
-
const TEMPLATES_DIR = 'templates';
|
|
17287
|
-
/** Registry cache file name. */
|
|
17288
|
-
const REGISTRY_CACHE_FILE = 'registry-cache.json';
|
|
17289
|
-
/** Core config file name. */
|
|
17290
|
-
const CONFIG_FILE = 'config.json';
|
|
17291
|
-
|
|
17292
18613
|
/**
|
|
17293
18614
|
* Default port assignments for Jeeves platform services.
|
|
17294
18615
|
*
|
|
@@ -17351,14 +18672,14 @@ const SECTION_ORDER = [
|
|
|
17351
18672
|
* Core library version, inlined at build time.
|
|
17352
18673
|
*
|
|
17353
18674
|
* @remarks
|
|
17354
|
-
* The `0.1.
|
|
18675
|
+
* The `0.1.6` placeholder is replaced by
|
|
17355
18676
|
* `@rollup/plugin-replace` during the build with the actual version
|
|
17356
18677
|
* from `package.json`. This ensures the correct version survives
|
|
17357
18678
|
* when consumers bundle core into their own dist (where runtime
|
|
17358
18679
|
* `import.meta.url`-based resolution would find the wrong package.json).
|
|
17359
18680
|
*/
|
|
17360
18681
|
/** The core library version from package.json (inlined at build time). */
|
|
17361
|
-
const CORE_VERSION = '0.1.
|
|
18682
|
+
const CORE_VERSION = '0.1.6';
|
|
17362
18683
|
|
|
17363
18684
|
/**
|
|
17364
18685
|
* Workspace and config root initialization.
|
|
@@ -17670,10 +18991,6 @@ function shouldWrite(myVersion, existing, stalenessThresholdMs = STALENESS_THRES
|
|
|
17670
18991
|
*
|
|
17671
18992
|
* Provides file-level locking, version-stamp convergence, and atomic writes.
|
|
17672
18993
|
*/
|
|
17673
|
-
/** Default core version when none provided. */
|
|
17674
|
-
const DEFAULT_VERSION = '0.0.0';
|
|
17675
|
-
/** Stale lock threshold in ms (2 minutes). */
|
|
17676
|
-
const STALE_LOCK_MS = 120_000;
|
|
17677
18994
|
/**
|
|
17678
18995
|
* Update a managed section in a file.
|
|
17679
18996
|
*
|
|
@@ -17682,7 +18999,7 @@ const STALE_LOCK_MS = 120_000;
|
|
|
17682
18999
|
* @param options - Write mode and optional configuration.
|
|
17683
19000
|
*/
|
|
17684
19001
|
async function updateManagedSection(filePath, content, options = {}) {
|
|
17685
|
-
const { mode = 'block', sectionId, markers = TOOLS_MARKERS, coreVersion =
|
|
19002
|
+
const { mode = 'block', sectionId, markers = TOOLS_MARKERS, coreVersion = DEFAULT_CORE_VERSION, stalenessThresholdMs, } = options;
|
|
17686
19003
|
if (mode === 'section' && !sectionId) {
|
|
17687
19004
|
throw new Error('sectionId is required when mode is "section"');
|
|
17688
19005
|
}
|
|
@@ -17694,93 +19011,77 @@ async function updateManagedSection(filePath, content, options = {}) {
|
|
|
17694
19011
|
if (!existsSync(filePath)) {
|
|
17695
19012
|
writeFileSync(filePath, '', 'utf-8');
|
|
17696
19013
|
}
|
|
17697
|
-
let release;
|
|
17698
19014
|
try {
|
|
17699
|
-
|
|
17700
|
-
|
|
17701
|
-
|
|
17702
|
-
|
|
17703
|
-
|
|
17704
|
-
|
|
17705
|
-
|
|
17706
|
-
|
|
17707
|
-
|
|
17708
|
-
|
|
17709
|
-
|
|
17710
|
-
|
|
17711
|
-
|
|
17712
|
-
|
|
17713
|
-
|
|
17714
|
-
|
|
17715
|
-
newManagedBody = markers.title
|
|
17716
|
-
? `# ${markers.title}\n\n${content}`
|
|
17717
|
-
: content;
|
|
17718
|
-
}
|
|
17719
|
-
else {
|
|
17720
|
-
// Section mode: upsert the named section
|
|
17721
|
-
const sections = [...parsed.sections];
|
|
17722
|
-
const existingIdx = sections.findIndex((s) => s.id === sectionId);
|
|
17723
|
-
if (existingIdx >= 0) {
|
|
17724
|
-
sections[existingIdx] = { id: sectionId, content };
|
|
19015
|
+
await withFileLock(filePath, () => {
|
|
19016
|
+
const fileContent = readFileSync(filePath, 'utf-8');
|
|
19017
|
+
const parsed = parseManaged(fileContent, markers);
|
|
19018
|
+
// Version-stamp convergence check (block mode only).
|
|
19019
|
+
// In section mode, components always write their own sections — the version
|
|
19020
|
+
// stamp governs shared content convergence, not component-specific sections.
|
|
19021
|
+
if (mode === 'block' &&
|
|
19022
|
+
!shouldWrite(coreVersion, parsed.versionStamp, stalenessThresholdMs)) {
|
|
19023
|
+
return;
|
|
19024
|
+
}
|
|
19025
|
+
let newManagedBody;
|
|
19026
|
+
if (mode === 'block') {
|
|
19027
|
+
// Prepend H1 title if markers specify one
|
|
19028
|
+
newManagedBody = markers.title
|
|
19029
|
+
? `# ${markers.title}\n\n${content}`
|
|
19030
|
+
: content;
|
|
17725
19031
|
}
|
|
17726
19032
|
else {
|
|
17727
|
-
|
|
19033
|
+
// Section mode: upsert the named section
|
|
19034
|
+
const sections = [...parsed.sections];
|
|
19035
|
+
const existingIdx = sections.findIndex((s) => s.id === sectionId);
|
|
19036
|
+
if (existingIdx >= 0) {
|
|
19037
|
+
sections[existingIdx] = { id: sectionId, content };
|
|
19038
|
+
}
|
|
19039
|
+
else {
|
|
19040
|
+
sections.push({ id: sectionId, content });
|
|
19041
|
+
}
|
|
19042
|
+
sortSectionsByOrder(sections);
|
|
19043
|
+
const sectionText = sections
|
|
19044
|
+
.map((s) => `## ${s.id}\n\n${s.content}`)
|
|
19045
|
+
.join('\n\n');
|
|
19046
|
+
// Prepend H1 title if markers specify one
|
|
19047
|
+
newManagedBody = markers.title
|
|
19048
|
+
? `# ${markers.title}\n\n${sectionText}`
|
|
19049
|
+
: sectionText;
|
|
19050
|
+
}
|
|
19051
|
+
// Cleanup detection
|
|
19052
|
+
const userContent = parsed.userContent;
|
|
19053
|
+
const cleanupNeeded = needsCleanup(newManagedBody, userContent);
|
|
19054
|
+
// Build the full managed block
|
|
19055
|
+
const beginLine = formatBeginMarker(markers.begin, coreVersion);
|
|
19056
|
+
const endLine = formatEndMarker(markers.end);
|
|
19057
|
+
const parts = [];
|
|
19058
|
+
if (parsed.beforeContent) {
|
|
19059
|
+
parts.push(parsed.beforeContent);
|
|
19060
|
+
parts.push('');
|
|
19061
|
+
}
|
|
19062
|
+
parts.push(beginLine);
|
|
19063
|
+
if (cleanupNeeded) {
|
|
19064
|
+
parts.push('');
|
|
19065
|
+
parts.push(CLEANUP_FLAG);
|
|
17728
19066
|
}
|
|
17729
|
-
sortSectionsByOrder(sections);
|
|
17730
|
-
const sectionText = sections
|
|
17731
|
-
.map((s) => `## ${s.id}\n\n${s.content}`)
|
|
17732
|
-
.join('\n\n');
|
|
17733
|
-
// Prepend H1 title if markers specify one (e.g., "# Jeeves Platform Tools")
|
|
17734
|
-
newManagedBody = markers.title
|
|
17735
|
-
? `# ${markers.title}\n\n${sectionText}`
|
|
17736
|
-
: sectionText;
|
|
17737
|
-
}
|
|
17738
|
-
// Cleanup detection
|
|
17739
|
-
const userContent = parsed.userContent;
|
|
17740
|
-
const cleanupNeeded = needsCleanup(newManagedBody, userContent);
|
|
17741
|
-
// Build the full managed block
|
|
17742
|
-
const beginLine = formatBeginMarker(markers.begin, coreVersion);
|
|
17743
|
-
const endLine = formatEndMarker(markers.end);
|
|
17744
|
-
const parts = [];
|
|
17745
|
-
if (parsed.beforeContent) {
|
|
17746
|
-
parts.push(parsed.beforeContent);
|
|
17747
19067
|
parts.push('');
|
|
17748
|
-
|
|
17749
|
-
parts.push(beginLine);
|
|
17750
|
-
if (cleanupNeeded) {
|
|
19068
|
+
parts.push(newManagedBody);
|
|
17751
19069
|
parts.push('');
|
|
17752
|
-
parts.push(
|
|
17753
|
-
|
|
17754
|
-
|
|
17755
|
-
|
|
17756
|
-
|
|
17757
|
-
parts.push(endLine);
|
|
17758
|
-
if (userContent) {
|
|
19070
|
+
parts.push(endLine);
|
|
19071
|
+
if (userContent) {
|
|
19072
|
+
parts.push('');
|
|
19073
|
+
parts.push(userContent);
|
|
19074
|
+
}
|
|
17759
19075
|
parts.push('');
|
|
17760
|
-
parts.
|
|
17761
|
-
|
|
17762
|
-
|
|
17763
|
-
const newFileContent = parts.join('\n');
|
|
17764
|
-
// Atomic write: write to temp file, then rename
|
|
17765
|
-
const tempPath = join(dir, `.${String(Date.now())}.tmp`);
|
|
17766
|
-
writeFileSync(tempPath, newFileContent, 'utf-8');
|
|
17767
|
-
renameSync(tempPath, filePath);
|
|
19076
|
+
const newFileContent = parts.join('\n');
|
|
19077
|
+
atomicWrite(filePath, newFileContent);
|
|
19078
|
+
});
|
|
17768
19079
|
}
|
|
17769
19080
|
catch (err) {
|
|
17770
19081
|
// Log warning but don't throw — writer cycles are periodic
|
|
17771
19082
|
const message = err instanceof Error ? err.message : String(err);
|
|
17772
19083
|
console.warn(`jeeves-core: updateManagedSection failed for ${filePath}: ${message}`);
|
|
17773
19084
|
}
|
|
17774
|
-
finally {
|
|
17775
|
-
if (release) {
|
|
17776
|
-
try {
|
|
17777
|
-
await release();
|
|
17778
|
-
}
|
|
17779
|
-
catch {
|
|
17780
|
-
// Lock already released or file deleted — safe to ignore
|
|
17781
|
-
}
|
|
17782
|
-
}
|
|
17783
|
-
}
|
|
17784
19085
|
}
|
|
17785
19086
|
|
|
17786
19087
|
var agentsSectionContent = `## Memory Architecture
|
|
@@ -18213,7 +19514,7 @@ function loadConfig(configDir) {
|
|
|
18213
19514
|
* @returns The resolved service URL.
|
|
18214
19515
|
* @throws Error if `init()` has not been called or the service is unknown.
|
|
18215
19516
|
*/
|
|
18216
|
-
function getServiceUrl(serviceName, consumerName) {
|
|
19517
|
+
function getServiceUrl$1(serviceName, consumerName) {
|
|
18217
19518
|
// 2. Check core config
|
|
18218
19519
|
const coreDir = getCoreConfigDir();
|
|
18219
19520
|
const coreConfig = loadConfig(coreDir);
|
|
@@ -18259,7 +19560,7 @@ function extractPort(url) {
|
|
|
18259
19560
|
* @returns Probe result.
|
|
18260
19561
|
*/
|
|
18261
19562
|
async function probeService(serviceName, consumerName, timeoutMs = 3000) {
|
|
18262
|
-
const url = getServiceUrl(serviceName);
|
|
19563
|
+
const url = getServiceUrl$1(serviceName);
|
|
18263
19564
|
const port = extractPort(url);
|
|
18264
19565
|
const endpoints = ['/status', '/health'];
|
|
18265
19566
|
for (const endpoint of endpoints) {
|
|
@@ -18364,6 +19665,63 @@ function checkRegistryVersion(packageName, cacheDir, ttlSeconds = 3600) {
|
|
|
18364
19665
|
}
|
|
18365
19666
|
}
|
|
18366
19667
|
|
|
19668
|
+
/**
|
|
19669
|
+
* Build enriched service rows for the Platform template.
|
|
19670
|
+
*
|
|
19671
|
+
* @remarks
|
|
19672
|
+
* Merges health probe results with component version state and
|
|
19673
|
+
* npm registry update availability into rows for the Handlebars
|
|
19674
|
+
* Platform template.
|
|
19675
|
+
*/
|
|
19676
|
+
/**
|
|
19677
|
+
* Check whether an available version is newer than the current one.
|
|
19678
|
+
*
|
|
19679
|
+
* @param available - Registry version string.
|
|
19680
|
+
* @param current - Currently installed version string.
|
|
19681
|
+
* @returns The available version if it's newer, otherwise undefined.
|
|
19682
|
+
*/
|
|
19683
|
+
function newerVersion(available, current) {
|
|
19684
|
+
if (!available ||
|
|
19685
|
+
!current ||
|
|
19686
|
+
!semver.valid(available) ||
|
|
19687
|
+
!semver.valid(current)) {
|
|
19688
|
+
return undefined;
|
|
19689
|
+
}
|
|
19690
|
+
return semver.gt(available, current) ? available : undefined;
|
|
19691
|
+
}
|
|
19692
|
+
/**
|
|
19693
|
+
* Build enriched service rows for the Platform Handlebars template.
|
|
19694
|
+
*
|
|
19695
|
+
* @param options - Probe results, version state, and configuration.
|
|
19696
|
+
* @returns Array of enriched service rows.
|
|
19697
|
+
*/
|
|
19698
|
+
function buildServiceRows(options) {
|
|
19699
|
+
const { probeResults, componentVersions, cacheDir, skipRegistryCheck } = options;
|
|
19700
|
+
return probeResults.map((r) => {
|
|
19701
|
+
const entry = componentVersions[r.name];
|
|
19702
|
+
if (!entry)
|
|
19703
|
+
return { ...r };
|
|
19704
|
+
let availableServiceVersion;
|
|
19705
|
+
let availablePluginVersion;
|
|
19706
|
+
if (!skipRegistryCheck) {
|
|
19707
|
+
if (entry.servicePackage) {
|
|
19708
|
+
const registryVersion = checkRegistryVersion(entry.servicePackage, cacheDir);
|
|
19709
|
+
availableServiceVersion = newerVersion(registryVersion, r.version);
|
|
19710
|
+
}
|
|
19711
|
+
if (entry.pluginPackage && entry.pluginVersion) {
|
|
19712
|
+
const registryVersion = checkRegistryVersion(entry.pluginPackage, cacheDir);
|
|
19713
|
+
availablePluginVersion = newerVersion(registryVersion, entry.pluginVersion);
|
|
19714
|
+
}
|
|
19715
|
+
}
|
|
19716
|
+
return {
|
|
19717
|
+
...r,
|
|
19718
|
+
pluginVersion: entry.pluginVersion,
|
|
19719
|
+
availableServiceVersion,
|
|
19720
|
+
availablePluginVersion,
|
|
19721
|
+
};
|
|
19722
|
+
});
|
|
19723
|
+
}
|
|
19724
|
+
|
|
18367
19725
|
/**
|
|
18368
19726
|
* Internal function to maintain SOUL.md, AGENTS.md, and TOOLS.md Platform section.
|
|
18369
19727
|
*
|
|
@@ -18417,15 +19775,28 @@ function copyTemplates(coreConfigDir) {
|
|
|
18417
19775
|
}
|
|
18418
19776
|
/** Whether Handlebars helpers have been registered. */
|
|
18419
19777
|
let helpersRegistered = false;
|
|
18420
|
-
/**
|
|
18421
|
-
* Register Handlebars helpers used in the Platform template.
|
|
18422
|
-
*/
|
|
19778
|
+
/** Register Handlebars helpers used in the Platform template. */
|
|
18423
19779
|
function registerHelpers() {
|
|
18424
19780
|
if (helpersRegistered)
|
|
18425
19781
|
return;
|
|
18426
19782
|
helpersRegistered = true;
|
|
18427
19783
|
Handlebars.registerHelper('gt', (a, b) => typeof a === 'number' && typeof b === 'number' && a > b);
|
|
18428
19784
|
}
|
|
19785
|
+
/**
|
|
19786
|
+
* Check if a newer core version is available on npm.
|
|
19787
|
+
*
|
|
19788
|
+
* @returns The newer version string, or undefined.
|
|
19789
|
+
*/
|
|
19790
|
+
function checkCoreUpdate(coreVersion, cacheDir) {
|
|
19791
|
+
const registryVersion = checkRegistryVersion('@karmaniverous/jeeves', cacheDir);
|
|
19792
|
+
if (registryVersion &&
|
|
19793
|
+
semver.valid(registryVersion) &&
|
|
19794
|
+
semver.valid(coreVersion) &&
|
|
19795
|
+
semver.gt(registryVersion, coreVersion)) {
|
|
19796
|
+
return registryVersion;
|
|
19797
|
+
}
|
|
19798
|
+
return undefined;
|
|
19799
|
+
}
|
|
18429
19800
|
/**
|
|
18430
19801
|
* Refresh platform content: SOUL.md, AGENTS.md, and TOOLS.md Platform section.
|
|
18431
19802
|
*
|
|
@@ -18437,55 +19808,46 @@ async function refreshPlatformContent(options) {
|
|
|
18437
19808
|
const coreConfigDir = getCoreConfigDir();
|
|
18438
19809
|
// 1. Probe all services
|
|
18439
19810
|
const probeResults = await probeAllServices(undefined, probeTimeoutMs);
|
|
18440
|
-
|
|
18441
|
-
|
|
19811
|
+
// 2. Write calling component's version entry (with serviceVersion from probe)
|
|
19812
|
+
if (componentName) {
|
|
19813
|
+
const callerProbe = probeResults.find((r) => r.name === componentName);
|
|
19814
|
+
writeComponentVersion(coreConfigDir, {
|
|
19815
|
+
componentName,
|
|
19816
|
+
serviceVersion: callerProbe?.version,
|
|
19817
|
+
pluginVersion: componentVersion,
|
|
19818
|
+
servicePackage,
|
|
19819
|
+
pluginPackage,
|
|
19820
|
+
});
|
|
19821
|
+
}
|
|
19822
|
+
// 3. Read all component versions from the shared state file
|
|
19823
|
+
const componentVersions = readComponentVersions(coreConfigDir);
|
|
19824
|
+
// 4. Build enriched service rows with registry checks
|
|
18442
19825
|
const cacheDir = componentName
|
|
18443
19826
|
? getComponentConfigDir(componentName)
|
|
18444
19827
|
: coreConfigDir;
|
|
18445
|
-
|
|
18446
|
-
|
|
18447
|
-
|
|
18448
|
-
|
|
18449
|
-
|
|
18450
|
-
|
|
18451
|
-
|
|
18452
|
-
|
|
18453
|
-
|
|
18454
|
-
|
|
18455
|
-
if (svcVersion) {
|
|
18456
|
-
availableServiceVersion = svcVersion;
|
|
18457
|
-
}
|
|
18458
|
-
}
|
|
18459
|
-
if (pluginPackage) {
|
|
18460
|
-
const plgVersion = checkRegistryVersion(pluginPackage, cacheDir);
|
|
18461
|
-
if (plgVersion) {
|
|
18462
|
-
availablePluginVersion = plgVersion;
|
|
18463
|
-
}
|
|
18464
|
-
}
|
|
18465
|
-
}
|
|
18466
|
-
// 3. Build enriched service rows — match the calling component by name
|
|
18467
|
-
const serviceRows = probeResults.map((r) => ({
|
|
18468
|
-
...r,
|
|
18469
|
-
pluginVersion: r.name === componentName ? componentVersion : undefined,
|
|
18470
|
-
availableServiceVersion: r.name === componentName ? availableServiceVersion : undefined,
|
|
18471
|
-
availablePluginVersion: r.name === componentName ? availablePluginVersion : undefined,
|
|
18472
|
-
}));
|
|
18473
|
-
// 5. Check if templates are available
|
|
19828
|
+
const availableCoreVersion = skipRegistryCheck
|
|
19829
|
+
? undefined
|
|
19830
|
+
: checkCoreUpdate(coreVersion, cacheDir);
|
|
19831
|
+
const serviceRows = buildServiceRows({
|
|
19832
|
+
probeResults,
|
|
19833
|
+
componentVersions,
|
|
19834
|
+
cacheDir,
|
|
19835
|
+
skipRegistryCheck,
|
|
19836
|
+
});
|
|
19837
|
+
// 5. Render Platform template
|
|
18474
19838
|
const templatePath = join(coreConfigDir, TEMPLATES_DIR);
|
|
18475
|
-
const templatesAvailable = existsSync(templatePath);
|
|
18476
|
-
// 6. Render Platform template
|
|
18477
19839
|
registerHelpers();
|
|
18478
19840
|
const template = Handlebars.compile(toolsPlatformTemplate);
|
|
18479
19841
|
const templateData = {
|
|
18480
19842
|
services: serviceRows,
|
|
18481
|
-
unhealthyServices,
|
|
19843
|
+
unhealthyServices: serviceRows.filter((r) => !r.healthy),
|
|
18482
19844
|
coreVersion,
|
|
18483
19845
|
availableCoreVersion,
|
|
18484
|
-
templatesAvailable,
|
|
19846
|
+
templatesAvailable: existsSync(templatePath),
|
|
18485
19847
|
templatePath,
|
|
18486
19848
|
};
|
|
18487
19849
|
const platformContent = template(templateData);
|
|
18488
|
-
//
|
|
19850
|
+
// 6. Write TOOLS.md Platform section
|
|
18489
19851
|
const toolsPath = join(workspacePath, WORKSPACE_FILES.tools);
|
|
18490
19852
|
await updateManagedSection(toolsPath, platformContent, {
|
|
18491
19853
|
mode: 'section',
|
|
@@ -18494,7 +19856,7 @@ async function refreshPlatformContent(options) {
|
|
|
18494
19856
|
coreVersion,
|
|
18495
19857
|
stalenessThresholdMs,
|
|
18496
19858
|
});
|
|
18497
|
-
//
|
|
19859
|
+
// 7. Write SOUL.md managed block
|
|
18498
19860
|
const soulPath = join(workspacePath, WORKSPACE_FILES.soul);
|
|
18499
19861
|
await updateManagedSection(soulPath, soulSectionContent, {
|
|
18500
19862
|
mode: 'block',
|
|
@@ -18502,7 +19864,7 @@ async function refreshPlatformContent(options) {
|
|
|
18502
19864
|
coreVersion,
|
|
18503
19865
|
stalenessThresholdMs,
|
|
18504
19866
|
});
|
|
18505
|
-
//
|
|
19867
|
+
// 8. Write AGENTS.md managed block
|
|
18506
19868
|
const agentsPath = join(workspacePath, WORKSPACE_FILES.agents);
|
|
18507
19869
|
await updateManagedSection(agentsPath, agentsSectionContent, {
|
|
18508
19870
|
mode: 'block',
|
|
@@ -18510,7 +19872,7 @@ async function refreshPlatformContent(options) {
|
|
|
18510
19872
|
coreVersion,
|
|
18511
19873
|
stalenessThresholdMs,
|
|
18512
19874
|
});
|
|
18513
|
-
//
|
|
19875
|
+
// 9. Copy templates to config dir
|
|
18514
19876
|
copyTemplates(coreConfigDir);
|
|
18515
19877
|
}
|
|
18516
19878
|
|
|
@@ -18590,6 +19952,8 @@ class ComponentWriter {
|
|
|
18590
19952
|
coreVersion: CORE_VERSION,
|
|
18591
19953
|
});
|
|
18592
19954
|
// Platform content maintenance: SOUL.md, AGENTS.md, Platform section
|
|
19955
|
+
// refreshPlatformContent also writes the component version entry
|
|
19956
|
+
// (with serviceVersion from probe) to the shared state file.
|
|
18593
19957
|
await refreshPlatformContent({
|
|
18594
19958
|
coreVersion: CORE_VERSION,
|
|
18595
19959
|
componentName: this.component.name,
|
|
@@ -18744,88 +20108,204 @@ function createComponentWriter(component, options) {
|
|
|
18744
20108
|
}
|
|
18745
20109
|
|
|
18746
20110
|
/**
|
|
18747
|
-
*
|
|
20111
|
+
* Plugin resolution helpers for the OpenClaw plugin SDK.
|
|
18748
20112
|
*
|
|
18749
20113
|
* @remarks
|
|
18750
|
-
*
|
|
18751
|
-
*
|
|
18752
|
-
*
|
|
18753
|
-
* 3. `process.cwd()` — last resort (unsafe when gateway runs from system32)
|
|
18754
|
-
*
|
|
18755
|
-
* The config value is checked first because `api.resolvePath('.')` delegates
|
|
18756
|
-
* to `path.resolve('.')`, which returns `process.cwd()` — not the workspace.
|
|
18757
|
-
* When the gateway runs as a Windows service from `C:\Windows\system32`,
|
|
18758
|
-
* `resolvePath('.')` returns system32, not the configured workspace.
|
|
18759
|
-
*
|
|
18760
|
-
* Plugins should call this once at registration time and pass the result
|
|
18761
|
-
* to `init({ workspacePath })`.
|
|
20114
|
+
* Provides workspace path resolution and plugin setting resolution
|
|
20115
|
+
* with a standard three-step fallback chain:
|
|
20116
|
+
* plugin config → environment variable → default value.
|
|
18762
20117
|
*/
|
|
18763
20118
|
/**
|
|
18764
20119
|
* Resolve the workspace root from the OpenClaw plugin API.
|
|
18765
20120
|
*
|
|
18766
|
-
* @
|
|
20121
|
+
* @remarks
|
|
20122
|
+
* Tries three sources in order:
|
|
20123
|
+
* 1. `api.config.agents.defaults.workspace` — explicit config
|
|
20124
|
+
* 2. `api.resolvePath('.')` — gateway-provided path resolver
|
|
20125
|
+
* 3. `process.cwd()` — last resort
|
|
20126
|
+
*
|
|
20127
|
+
* @param api - The plugin API object provided by the gateway.
|
|
18767
20128
|
* @returns Absolute path to the workspace root.
|
|
18768
20129
|
*/
|
|
18769
20130
|
function resolveWorkspacePath(api) {
|
|
18770
|
-
// 1. Explicit config value (most authoritative)
|
|
18771
20131
|
const configured = api.config?.agents?.defaults?.workspace;
|
|
18772
20132
|
if (typeof configured === 'string' && configured.trim()) {
|
|
18773
20133
|
return configured;
|
|
18774
20134
|
}
|
|
18775
|
-
// 2. Gateway-provided path resolver
|
|
18776
20135
|
if (typeof api.resolvePath === 'function') {
|
|
18777
20136
|
return api.resolvePath('.');
|
|
18778
20137
|
}
|
|
18779
|
-
// 3. Last resort — unsafe when gateway runs from system32
|
|
18780
20138
|
return process.cwd();
|
|
18781
20139
|
}
|
|
18782
|
-
|
|
18783
20140
|
/**
|
|
18784
|
-
*
|
|
20141
|
+
* Resolve a plugin setting via the standard three-step fallback chain:
|
|
20142
|
+
* plugin config → environment variable → fallback value.
|
|
20143
|
+
*
|
|
20144
|
+
* @param api - Plugin API object.
|
|
20145
|
+
* @param pluginId - Plugin identifier (e.g., 'jeeves-watcher-openclaw').
|
|
20146
|
+
* @param key - Config key within the plugin's config object.
|
|
20147
|
+
* @param envVar - Environment variable name.
|
|
20148
|
+
* @param fallback - Default value if neither source provides one.
|
|
20149
|
+
* @returns The resolved setting value.
|
|
18785
20150
|
*/
|
|
18786
|
-
|
|
18787
|
-
const
|
|
20151
|
+
function resolvePluginSetting(api, pluginId, key, envVar, fallback) {
|
|
20152
|
+
const fromPlugin = api.config?.plugins?.entries?.[pluginId]?.config?.[key];
|
|
20153
|
+
if (typeof fromPlugin === 'string')
|
|
20154
|
+
return fromPlugin;
|
|
20155
|
+
const fromEnv = process.env[envVar];
|
|
20156
|
+
if (fromEnv)
|
|
20157
|
+
return fromEnv;
|
|
20158
|
+
return fallback;
|
|
20159
|
+
}
|
|
18788
20160
|
|
|
18789
20161
|
/**
|
|
18790
|
-
*
|
|
20162
|
+
* HTTP helpers for the OpenClaw plugin SDK.
|
|
20163
|
+
*
|
|
20164
|
+
* @remarks
|
|
20165
|
+
* Thin wrappers around `fetch` that throw on non-OK responses
|
|
20166
|
+
* and handle JSON serialisation/deserialisation.
|
|
20167
|
+
*/
|
|
20168
|
+
/**
|
|
20169
|
+
* Fetch JSON from a URL, throwing on non-OK responses.
|
|
20170
|
+
*
|
|
20171
|
+
* @param url - URL to fetch.
|
|
20172
|
+
* @param init - Optional `fetch` init options.
|
|
20173
|
+
* @returns Parsed JSON response body.
|
|
20174
|
+
* @throws Error with `HTTP {status}: {body}` message on non-OK responses.
|
|
18791
20175
|
*/
|
|
18792
|
-
|
|
18793
|
-
|
|
18794
|
-
|
|
18795
|
-
|
|
20176
|
+
async function fetchJson(url, init) {
|
|
20177
|
+
const res = await fetch(url, init);
|
|
20178
|
+
if (!res.ok) {
|
|
20179
|
+
throw new Error('HTTP ' + String(res.status) + ': ' + (await res.text()));
|
|
20180
|
+
}
|
|
20181
|
+
return res.json();
|
|
18796
20182
|
}
|
|
18797
|
-
|
|
18798
|
-
|
|
18799
|
-
|
|
18800
|
-
|
|
20183
|
+
|
|
20184
|
+
/**
|
|
20185
|
+
* OpenClaw configuration helpers for plugin CLI installers.
|
|
20186
|
+
*
|
|
20187
|
+
* @remarks
|
|
20188
|
+
* Provides resolution of OpenClaw home directory and config file path,
|
|
20189
|
+
* plus idempotent config patching for plugin install/uninstall.
|
|
20190
|
+
*/
|
|
20191
|
+
/**
|
|
20192
|
+
* Resolve the OpenClaw home directory.
|
|
20193
|
+
*
|
|
20194
|
+
* @remarks
|
|
20195
|
+
* Resolution order:
|
|
20196
|
+
* 1. `OPENCLAW_CONFIG` env var → dirname of the config file path
|
|
20197
|
+
* 2. `OPENCLAW_HOME` env var → resolved path
|
|
20198
|
+
* 3. Default: `~/.openclaw`
|
|
20199
|
+
*
|
|
20200
|
+
* @returns Absolute path to the OpenClaw home directory.
|
|
20201
|
+
*/
|
|
20202
|
+
function resolveOpenClawHome() {
|
|
20203
|
+
if (process.env.OPENCLAW_CONFIG) {
|
|
20204
|
+
return dirname(resolve(process.env.OPENCLAW_CONFIG));
|
|
20205
|
+
}
|
|
20206
|
+
if (process.env.OPENCLAW_HOME) {
|
|
20207
|
+
return resolve(process.env.OPENCLAW_HOME);
|
|
20208
|
+
}
|
|
20209
|
+
return join(homedir(), '.openclaw');
|
|
18801
20210
|
}
|
|
18802
|
-
/**
|
|
18803
|
-
|
|
18804
|
-
|
|
18805
|
-
|
|
20211
|
+
/**
|
|
20212
|
+
* Resolve the OpenClaw config file path.
|
|
20213
|
+
*
|
|
20214
|
+
* @remarks
|
|
20215
|
+
* If `OPENCLAW_CONFIG` is set, uses that directly.
|
|
20216
|
+
* Otherwise defaults to `{home}/openclaw.json`.
|
|
20217
|
+
*
|
|
20218
|
+
* @param home - The OpenClaw home directory.
|
|
20219
|
+
* @returns Absolute path to the config file.
|
|
20220
|
+
*/
|
|
20221
|
+
function resolveConfigPath(home) {
|
|
20222
|
+
if (process.env.OPENCLAW_CONFIG) {
|
|
20223
|
+
return resolve(process.env.OPENCLAW_CONFIG);
|
|
20224
|
+
}
|
|
20225
|
+
return join(home, 'openclaw.json');
|
|
18806
20226
|
}
|
|
18807
|
-
/**
|
|
18808
|
-
|
|
18809
|
-
|
|
18810
|
-
|
|
18811
|
-
|
|
18812
|
-
|
|
20227
|
+
/**
|
|
20228
|
+
* Patch an allowlist array: add or remove the plugin ID.
|
|
20229
|
+
*
|
|
20230
|
+
* @returns A log message if a change was made, or undefined.
|
|
20231
|
+
*/
|
|
20232
|
+
function patchAllowList(parent, key, label, pluginId, mode) {
|
|
20233
|
+
{
|
|
20234
|
+
if (!Array.isArray(parent[key]))
|
|
20235
|
+
return undefined;
|
|
20236
|
+
const list = parent[key];
|
|
20237
|
+
const filtered = list.filter((id) => id !== pluginId);
|
|
20238
|
+
if (filtered.length !== list.length) {
|
|
20239
|
+
parent[key] = filtered;
|
|
20240
|
+
return `Removed "${pluginId}" from ${label}`;
|
|
20241
|
+
}
|
|
20242
|
+
}
|
|
20243
|
+
return undefined;
|
|
18813
20244
|
}
|
|
18814
|
-
/**
|
|
18815
|
-
|
|
18816
|
-
|
|
18817
|
-
|
|
18818
|
-
|
|
18819
|
-
|
|
18820
|
-
|
|
20245
|
+
/**
|
|
20246
|
+
* Patch an OpenClaw config for plugin install or uninstall.
|
|
20247
|
+
*
|
|
20248
|
+
* @remarks
|
|
20249
|
+
* Manages `plugins.entries.{pluginId}` and `tools.alsoAllow`.
|
|
20250
|
+
* Idempotent: adding twice produces no duplicates; removing when absent
|
|
20251
|
+
* produces no errors.
|
|
20252
|
+
*
|
|
20253
|
+
* @param config - The parsed OpenClaw config object (mutated in place).
|
|
20254
|
+
* @param pluginId - The plugin identifier.
|
|
20255
|
+
* @param mode - Whether to add or remove the plugin.
|
|
20256
|
+
* @returns Array of log messages describing changes made.
|
|
20257
|
+
*/
|
|
20258
|
+
function patchConfig(config, pluginId, mode) {
|
|
20259
|
+
const messages = [];
|
|
20260
|
+
// Ensure plugins section
|
|
20261
|
+
if (!config.plugins || typeof config.plugins !== 'object') {
|
|
20262
|
+
config.plugins = {};
|
|
20263
|
+
}
|
|
20264
|
+
const plugins = config.plugins;
|
|
20265
|
+
// plugins.entries
|
|
20266
|
+
if (!plugins.entries || typeof plugins.entries !== 'object') {
|
|
20267
|
+
plugins.entries = {};
|
|
20268
|
+
}
|
|
20269
|
+
const entries = plugins.entries;
|
|
20270
|
+
if (pluginId in entries) {
|
|
20271
|
+
Reflect.deleteProperty(entries, pluginId);
|
|
20272
|
+
messages.push(`Removed "${pluginId}" from plugins.entries`);
|
|
20273
|
+
}
|
|
20274
|
+
// tools.alsoAllow
|
|
20275
|
+
if (!config.tools || typeof config.tools !== 'object') {
|
|
20276
|
+
config.tools = {};
|
|
20277
|
+
}
|
|
20278
|
+
const tools = config.tools;
|
|
20279
|
+
const toolAlsoAllow = patchAllowList(tools, 'alsoAllow', 'tools.alsoAllow', pluginId);
|
|
20280
|
+
if (toolAlsoAllow)
|
|
20281
|
+
messages.push(toolAlsoAllow);
|
|
20282
|
+
return messages;
|
|
18821
20283
|
}
|
|
18822
|
-
|
|
20284
|
+
|
|
20285
|
+
/**
|
|
20286
|
+
* Tool result formatters for the OpenClaw plugin SDK.
|
|
20287
|
+
*
|
|
20288
|
+
* @remarks
|
|
20289
|
+
* Provides standardised helpers for building `ToolResult` objects:
|
|
20290
|
+
* success, error, and connection-error variants.
|
|
20291
|
+
*/
|
|
20292
|
+
/**
|
|
20293
|
+
* Format a successful tool result.
|
|
20294
|
+
*
|
|
20295
|
+
* @param data - Arbitrary data to return as JSON.
|
|
20296
|
+
* @returns A `ToolResult` with JSON-stringified content.
|
|
20297
|
+
*/
|
|
18823
20298
|
function ok(data) {
|
|
18824
20299
|
return {
|
|
18825
20300
|
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
18826
20301
|
};
|
|
18827
20302
|
}
|
|
18828
|
-
/**
|
|
20303
|
+
/**
|
|
20304
|
+
* Format an error tool result.
|
|
20305
|
+
*
|
|
20306
|
+
* @param error - Error instance, string, or other value.
|
|
20307
|
+
* @returns A `ToolResult` with `isError: true`.
|
|
20308
|
+
*/
|
|
18829
20309
|
function fail(error) {
|
|
18830
20310
|
const message = error instanceof Error ? error.message : String(error);
|
|
18831
20311
|
return {
|
|
@@ -18833,8 +20313,21 @@ function fail(error) {
|
|
|
18833
20313
|
isError: true,
|
|
18834
20314
|
};
|
|
18835
20315
|
}
|
|
18836
|
-
/**
|
|
18837
|
-
|
|
20316
|
+
/**
|
|
20317
|
+
* Format a connection error with actionable guidance.
|
|
20318
|
+
*
|
|
20319
|
+
* @remarks
|
|
20320
|
+
* Detects `ECONNREFUSED`, `ENOTFOUND`, and `ETIMEDOUT` from
|
|
20321
|
+
* `error.cause.code` and returns a user-friendly message referencing
|
|
20322
|
+
* the plugin's `config.apiUrl` setting. Falls back to `fail()` for
|
|
20323
|
+
* non-connection errors.
|
|
20324
|
+
*
|
|
20325
|
+
* @param error - Error instance (typically from `fetch`).
|
|
20326
|
+
* @param baseUrl - The URL that was being contacted.
|
|
20327
|
+
* @param pluginId - The plugin identifier for config guidance.
|
|
20328
|
+
* @returns A `ToolResult` with `isError: true`.
|
|
20329
|
+
*/
|
|
20330
|
+
function connectionFail(error, baseUrl, pluginId) {
|
|
18838
20331
|
const cause = error instanceof Error ? error.cause : undefined;
|
|
18839
20332
|
const code = cause && typeof cause === 'object' && 'code' in cause
|
|
18840
20333
|
? String(cause.code)
|
|
@@ -18846,9 +20339,9 @@ function connectionFail(error, baseUrl) {
|
|
|
18846
20339
|
{
|
|
18847
20340
|
type: 'text',
|
|
18848
20341
|
text: [
|
|
18849
|
-
|
|
18850
|
-
'Either start the
|
|
18851
|
-
|
|
20342
|
+
`Service not reachable at ${baseUrl}.`,
|
|
20343
|
+
'Either start the service, or if it runs on a different port,',
|
|
20344
|
+
`set plugins.entries.${pluginId}.config.apiUrl in openclaw.json.`,
|
|
18852
20345
|
].join('\n'),
|
|
18853
20346
|
},
|
|
18854
20347
|
],
|
|
@@ -18857,14 +20350,12 @@ function connectionFail(error, baseUrl) {
|
|
|
18857
20350
|
}
|
|
18858
20351
|
return fail(error);
|
|
18859
20352
|
}
|
|
18860
|
-
|
|
18861
|
-
|
|
18862
|
-
|
|
18863
|
-
|
|
18864
|
-
|
|
18865
|
-
|
|
18866
|
-
return res.json();
|
|
18867
|
-
}
|
|
20353
|
+
|
|
20354
|
+
/**
|
|
20355
|
+
* Shared constants for the jeeves-server OpenClaw plugin.
|
|
20356
|
+
*/
|
|
20357
|
+
/** Plugin identifier used in OpenClaw config and extension paths. */
|
|
20358
|
+
const PLUGIN_ID = 'jeeves-server-openclaw';
|
|
18868
20359
|
|
|
18869
20360
|
/**
|
|
18870
20361
|
* Generates the Server menu string for TOOLS.md injection.
|
|
@@ -18961,6 +20452,38 @@ async function generateServerMenu(apiUrl) {
|
|
|
18961
20452
|
return lines.join('\n');
|
|
18962
20453
|
}
|
|
18963
20454
|
|
|
20455
|
+
/**
|
|
20456
|
+
* Server-specific HMAC authentication helpers for the OpenClaw plugin.
|
|
20457
|
+
*
|
|
20458
|
+
* @remarks
|
|
20459
|
+
* These helpers handle insider key derivation and URL signing for
|
|
20460
|
+
* authenticated requests to jeeves-server. Generic helpers (ok, fail,
|
|
20461
|
+
* connectionFail, fetchJson, etc.) are imported from `@karmaniverous/jeeves`.
|
|
20462
|
+
*
|
|
20463
|
+
* @packageDocumentation
|
|
20464
|
+
*/
|
|
20465
|
+
/** Resolve the plugin key seed from plugin config. */
|
|
20466
|
+
function getPluginKey(api) {
|
|
20467
|
+
const config = api.config?.plugins?.entries?.[PLUGIN_ID]?.config;
|
|
20468
|
+
const key = config?.pluginKey;
|
|
20469
|
+
return typeof key === 'string' ? key : undefined;
|
|
20470
|
+
}
|
|
20471
|
+
/** Derive HMAC key from seed. */
|
|
20472
|
+
function deriveKey(seed) {
|
|
20473
|
+
return createHmac('sha256', seed)
|
|
20474
|
+
.update('insider')
|
|
20475
|
+
.digest('hex')
|
|
20476
|
+
.substring(0, 32);
|
|
20477
|
+
}
|
|
20478
|
+
/** Append auth key query param to a URL. */
|
|
20479
|
+
function withAuth(url, keySeed) {
|
|
20480
|
+
if (!keySeed)
|
|
20481
|
+
return url;
|
|
20482
|
+
const derived = deriveKey(keySeed);
|
|
20483
|
+
const sep = url.includes('?') ? '&' : '?';
|
|
20484
|
+
return url + sep + 'key=' + derived;
|
|
20485
|
+
}
|
|
20486
|
+
|
|
18964
20487
|
/**
|
|
18965
20488
|
* Server tool registrations (server_* tools) for the OpenClaw plugin.
|
|
18966
20489
|
*/
|
|
@@ -18990,7 +20513,7 @@ function registerApiTool(api, baseUrl, keySeed, config) {
|
|
|
18990
20513
|
return ok(data);
|
|
18991
20514
|
}
|
|
18992
20515
|
catch (error) {
|
|
18993
|
-
return connectionFail(error, baseUrl);
|
|
20516
|
+
return connectionFail(error, baseUrl, PLUGIN_ID);
|
|
18994
20517
|
}
|
|
18995
20518
|
},
|
|
18996
20519
|
}, { optional: true });
|
|
@@ -19121,75 +20644,6 @@ function registerServerTools(api, baseUrl) {
|
|
|
19121
20644
|
}
|
|
19122
20645
|
}
|
|
19123
20646
|
|
|
19124
|
-
/**
|
|
19125
|
-
* OpenClaw home and config path resolution.
|
|
19126
|
-
*
|
|
19127
|
-
* @remarks
|
|
19128
|
-
* Shared by the CLI installer and the in-process plugin commands.
|
|
19129
|
-
*/
|
|
19130
|
-
/**
|
|
19131
|
-
* Resolve the OpenClaw home directory from environment or default.
|
|
19132
|
-
*/
|
|
19133
|
-
function resolveOpenClawHome() {
|
|
19134
|
-
if (process.env.OPENCLAW_CONFIG)
|
|
19135
|
-
return dirname(resolve(process.env.OPENCLAW_CONFIG));
|
|
19136
|
-
if (process.env.OPENCLAW_HOME)
|
|
19137
|
-
return resolve(process.env.OPENCLAW_HOME);
|
|
19138
|
-
return join(homedir(), '.openclaw');
|
|
19139
|
-
}
|
|
19140
|
-
/**
|
|
19141
|
-
* Resolve the OpenClaw config file path from environment or default.
|
|
19142
|
-
*/
|
|
19143
|
-
function resolveConfigPath(home) {
|
|
19144
|
-
if (process.env.OPENCLAW_CONFIG)
|
|
19145
|
-
return resolve(process.env.OPENCLAW_CONFIG);
|
|
19146
|
-
return join(home, 'openclaw.json');
|
|
19147
|
-
}
|
|
19148
|
-
|
|
19149
|
-
/**
|
|
19150
|
-
* OpenClaw config patching utilities.
|
|
19151
|
-
*
|
|
19152
|
-
* @remarks
|
|
19153
|
-
* Shared by both the `npx ... install|uninstall` CLI and by in-process
|
|
19154
|
-
* plugin commands.
|
|
19155
|
-
*/
|
|
19156
|
-
function patchAllowList(parent, key, label, mode) {
|
|
19157
|
-
if (!Array.isArray(parent[key]) || parent[key].length === 0)
|
|
19158
|
-
return undefined;
|
|
19159
|
-
const list = parent[key];
|
|
19160
|
-
{
|
|
19161
|
-
const filtered = list.filter((id) => id !== PLUGIN_ID);
|
|
19162
|
-
if (filtered.length !== list.length) {
|
|
19163
|
-
parent[key] = filtered;
|
|
19164
|
-
return 'Removed "' + PLUGIN_ID + '" from ' + label;
|
|
19165
|
-
}
|
|
19166
|
-
}
|
|
19167
|
-
return undefined;
|
|
19168
|
-
}
|
|
19169
|
-
function patchConfig(config, mode) {
|
|
19170
|
-
const messages = [];
|
|
19171
|
-
if (!config.plugins || typeof config.plugins !== 'object')
|
|
19172
|
-
config.plugins = {};
|
|
19173
|
-
const plugins = config.plugins;
|
|
19174
|
-
const pluginAllow = patchAllowList(plugins, 'allow', 'plugins.allow');
|
|
19175
|
-
if (pluginAllow)
|
|
19176
|
-
messages.push(pluginAllow);
|
|
19177
|
-
if (!plugins.entries || typeof plugins.entries !== 'object')
|
|
19178
|
-
plugins.entries = {};
|
|
19179
|
-
const entries = plugins.entries;
|
|
19180
|
-
if (PLUGIN_ID in entries) {
|
|
19181
|
-
Reflect.deleteProperty(entries, PLUGIN_ID);
|
|
19182
|
-
messages.push('Removed "' + PLUGIN_ID + '" from plugins.entries');
|
|
19183
|
-
}
|
|
19184
|
-
if (!config.tools || typeof config.tools !== 'object')
|
|
19185
|
-
config.tools = {};
|
|
19186
|
-
const tools = config.tools;
|
|
19187
|
-
const toolAllow = patchAllowList(tools, 'allow', 'tools.allow');
|
|
19188
|
-
if (toolAllow)
|
|
19189
|
-
messages.push(toolAllow);
|
|
19190
|
-
return messages;
|
|
19191
|
-
}
|
|
19192
|
-
|
|
19193
20647
|
/**
|
|
19194
20648
|
* Shared plugin removal logic for extension directory and config cleanup.
|
|
19195
20649
|
*
|
|
@@ -19214,7 +20668,7 @@ function removePlugin(home, configPath) {
|
|
|
19214
20668
|
if (existsSync(configPath)) {
|
|
19215
20669
|
const raw = readFileSync(configPath, 'utf8');
|
|
19216
20670
|
const config = JSON.parse(raw);
|
|
19217
|
-
const patchMessages = patchConfig(config);
|
|
20671
|
+
const patchMessages = patchConfig(config, PLUGIN_ID);
|
|
19218
20672
|
if (patchMessages.length > 0) {
|
|
19219
20673
|
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
19220
20674
|
messages.push(...patchMessages);
|
|
@@ -19307,18 +20761,17 @@ function createPluginCommands() {
|
|
|
19307
20761
|
/** Plugin version derived from package.json at runtime. */
|
|
19308
20762
|
const require$1 = createRequire(import.meta.url);
|
|
19309
20763
|
const { version: PLUGIN_VERSION } = require$1('../package.json');
|
|
19310
|
-
/** Default config root when not specified in plugin config. */
|
|
19311
|
-
const DEFAULT_CONFIG_ROOT = 'j:/config';
|
|
19312
20764
|
/** Refresh interval in seconds (must be prime). */
|
|
19313
20765
|
const REFRESH_INTERVAL_SECONDS = 61;
|
|
19314
20766
|
/** Active writer instance — stopped on re-registration to prevent leaks. */
|
|
19315
20767
|
let activeWriter = null;
|
|
19316
|
-
/**
|
|
19317
|
-
|
|
19318
|
-
|
|
20768
|
+
/** Resolve the server API base URL from plugin config or environment. */
|
|
20769
|
+
function getServiceUrl(api) {
|
|
20770
|
+
return resolvePluginSetting(api, PLUGIN_ID, 'apiUrl', 'JEEVES_SERVER_URL', 'http://127.0.0.1:1934');
|
|
20771
|
+
}
|
|
20772
|
+
/** Resolve the platform config root from plugin config or environment. */
|
|
19319
20773
|
function getConfigRoot(api) {
|
|
19320
|
-
|
|
19321
|
-
return typeof root === 'string' ? root : DEFAULT_CONFIG_ROOT;
|
|
20774
|
+
return resolvePluginSetting(api, PLUGIN_ID, 'configRoot', 'JEEVES_CONFIG_ROOT', 'j:/config');
|
|
19322
20775
|
}
|
|
19323
20776
|
/** Register all jeeves-server tools and start the TOOLS.md writer. */
|
|
19324
20777
|
function register(api) {
|
|
@@ -19327,7 +20780,7 @@ function register(api) {
|
|
|
19327
20780
|
activeWriter.stop();
|
|
19328
20781
|
activeWriter = null;
|
|
19329
20782
|
}
|
|
19330
|
-
const baseUrl =
|
|
20783
|
+
const baseUrl = getServiceUrl(api);
|
|
19331
20784
|
registerServerTools(api, baseUrl);
|
|
19332
20785
|
// Initialize jeeves-core
|
|
19333
20786
|
const workspacePath = resolveWorkspacePath(api);
|