@karmaniverous/jeeves 0.4.7 → 0.5.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/README.md +56 -3
- package/content/skill.md +105 -0
- package/dist/cli/jeeves/index.js +667 -159
- package/dist/cli/plugin/index.js +147 -5
- package/dist/cli/service/index.js +63 -7
- package/dist/index.d.ts +253 -9
- package/dist/index.js +779 -89
- package/package.json +2 -2
package/dist/cli/jeeves/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as commander from 'commander';
|
|
3
|
-
import {
|
|
3
|
+
import { major, valid, gte } from 'semver';
|
|
4
|
+
import { JSONPath } from 'jsonpath-plus';
|
|
5
|
+
import { join, resolve, dirname } from 'node:path';
|
|
4
6
|
import { existsSync, readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync, cpSync, rmSync } from 'node:fs';
|
|
5
|
-
import { join, dirname } from 'node:path';
|
|
6
|
-
import { valid, gte } from 'semver';
|
|
7
7
|
import { z } from 'zod';
|
|
8
|
+
import { execSync, spawnSync } from 'node:child_process';
|
|
8
9
|
import { lock } from 'proper-lockfile';
|
|
9
10
|
import { fileURLToPath } from 'node:url';
|
|
10
11
|
import { packageDirectorySync } from 'package-directory';
|
|
@@ -157,8 +158,8 @@ const ALL_MARKERS = [
|
|
|
157
158
|
const VERSION_STAMP_PATTERN = /<!--\s*(.+?)\s*\|\s*core:(\S+)\s*\|\s*(\S+)\s*-->/;
|
|
158
159
|
/** Staleness threshold for version-stamp convergence in milliseconds. */
|
|
159
160
|
const STALENESS_THRESHOLD_MS = 5 * 60 * 1000;
|
|
160
|
-
/** Warning text
|
|
161
|
-
const CLEANUP_FLAG = '> ⚠️ CLEANUP NEEDED: Orphaned Jeeves content
|
|
161
|
+
/** Warning text injected inside managed block when cleanup is needed. */
|
|
162
|
+
const CLEANUP_FLAG = '> ⚠️ CLEANUP NEEDED: Orphaned Jeeves content detected outside this managed block. Review the file and remove any content outside the BEGIN/END markers that duplicates what appears inside them.';
|
|
162
163
|
|
|
163
164
|
/**
|
|
164
165
|
* Directory and file path conventions for the Jeeves platform.
|
|
@@ -175,7 +176,13 @@ const WORKSPACE_FILES = {
|
|
|
175
176
|
agents: 'AGENTS.md',
|
|
176
177
|
/** HEARTBEAT.md — platform status and health alerts. */
|
|
177
178
|
heartbeat: 'HEARTBEAT.md',
|
|
179
|
+
/** MEMORY.md — curated long-term memory. */
|
|
180
|
+
memory: 'MEMORY.md',
|
|
178
181
|
};
|
|
182
|
+
/** Skill directory name within workspace. */
|
|
183
|
+
const SKILLS_DIR = 'skills';
|
|
184
|
+
/** Jeeves skill directory name. */
|
|
185
|
+
const JEEVES_SKILL_DIR = 'jeeves';
|
|
179
186
|
/** Templates directory name within core config. */
|
|
180
187
|
const TEMPLATES_DIR = 'templates';
|
|
181
188
|
/** Core config file name. */
|
|
@@ -261,14 +268,318 @@ const PLATFORM_COMPONENTS = [
|
|
|
261
268
|
* Core library version, inlined at build time.
|
|
262
269
|
*
|
|
263
270
|
* @remarks
|
|
264
|
-
* The `0.
|
|
271
|
+
* The `0.5.0` placeholder is replaced by
|
|
265
272
|
* `@rollup/plugin-replace` during the build with the actual version
|
|
266
273
|
* from `package.json`. This ensures the correct version survives
|
|
267
274
|
* when consumers bundle core into their own dist (where runtime
|
|
268
275
|
* `import.meta.url`-based resolution would find the wrong package.json).
|
|
269
276
|
*/
|
|
270
277
|
/** The core library version from package.json (inlined at build time). */
|
|
271
|
-
const CORE_VERSION = '0.
|
|
278
|
+
const CORE_VERSION = '0.5.0';
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Runtime Node.js version floor check.
|
|
282
|
+
*
|
|
283
|
+
* @module
|
|
284
|
+
*/
|
|
285
|
+
/** Minimum supported Node.js major version. */
|
|
286
|
+
const MIN_NODE_MAJOR = 22;
|
|
287
|
+
/**
|
|
288
|
+
* Check that the running Node.js version meets the minimum requirement.
|
|
289
|
+
* Prints an error and exits with code 1 if the check fails.
|
|
290
|
+
*/
|
|
291
|
+
function checkNodeVersion() {
|
|
292
|
+
const nodeMajor = major(process.versions.node);
|
|
293
|
+
if (nodeMajor < MIN_NODE_MAJOR) {
|
|
294
|
+
console.error(`Error: jeeves requires Node.js >= ${String(MIN_NODE_MAJOR)}. Current: ${process.versions.node}`);
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Workspace-level shared configuration: `jeeves.config.json`.
|
|
301
|
+
*
|
|
302
|
+
* @remarks
|
|
303
|
+
* Lives at the OpenClaw workspace root alongside TOOLS.md and SOUL.md.
|
|
304
|
+
* Provides namespaced shared defaults consumed by the root Jeeves CLI.
|
|
305
|
+
* Resolution precedence: CLI flags → env vars → jeeves.config.json → defaults.
|
|
306
|
+
*
|
|
307
|
+
* This does not replace component-owned config schemas (Decision 41).
|
|
308
|
+
*/
|
|
309
|
+
/** Workspace config file name. */
|
|
310
|
+
const WORKSPACE_CONFIG_FILE = 'jeeves.config.json';
|
|
311
|
+
/** Core shared config section. */
|
|
312
|
+
const workspaceCoreConfigSchema = z
|
|
313
|
+
.object({
|
|
314
|
+
/** Workspace root path. */
|
|
315
|
+
workspace: z.string().optional().describe('Workspace root path'),
|
|
316
|
+
/** Platform config root path. */
|
|
317
|
+
configRoot: z.string().optional().describe('Platform config root path'),
|
|
318
|
+
/** OpenClaw gateway URL. */
|
|
319
|
+
gatewayUrl: z.string().optional().describe('OpenClaw gateway URL'),
|
|
320
|
+
})
|
|
321
|
+
.partial();
|
|
322
|
+
/** Memory shared config section. */
|
|
323
|
+
const workspaceMemoryConfigSchema = z
|
|
324
|
+
.object({
|
|
325
|
+
/** MEMORY.md character budget. */
|
|
326
|
+
budget: z.number().int().positive().optional().describe('Memory budget'),
|
|
327
|
+
/** Warning threshold as a fraction of budget. */
|
|
328
|
+
warningThreshold: z
|
|
329
|
+
.number()
|
|
330
|
+
.min(0)
|
|
331
|
+
.max(1)
|
|
332
|
+
.optional()
|
|
333
|
+
.describe('Memory warning threshold'),
|
|
334
|
+
/** Staleness threshold in days. */
|
|
335
|
+
staleDays: z
|
|
336
|
+
.number()
|
|
337
|
+
.int()
|
|
338
|
+
.positive()
|
|
339
|
+
.optional()
|
|
340
|
+
.describe('Memory staleness threshold in days'),
|
|
341
|
+
})
|
|
342
|
+
.partial();
|
|
343
|
+
/** Workspace config Zod schema. */
|
|
344
|
+
const workspaceConfigSchema = z.object({
|
|
345
|
+
/** JSON Schema pointer for IDE autocomplete. */
|
|
346
|
+
$schema: z.string().optional().describe('JSON Schema pointer'),
|
|
347
|
+
/** Core shared defaults. */
|
|
348
|
+
core: workspaceCoreConfigSchema.optional(),
|
|
349
|
+
/** Memory hygiene shared defaults. */
|
|
350
|
+
memory: workspaceMemoryConfigSchema.optional(),
|
|
351
|
+
});
|
|
352
|
+
/** Built-in workspace config defaults. */
|
|
353
|
+
const WORKSPACE_CONFIG_DEFAULTS = {
|
|
354
|
+
core: {
|
|
355
|
+
workspace: '.',
|
|
356
|
+
configRoot: './config',
|
|
357
|
+
gatewayUrl: 'http://127.0.0.1:3000',
|
|
358
|
+
},
|
|
359
|
+
memory: {
|
|
360
|
+
budget: 20_000,
|
|
361
|
+
warningThreshold: 0.8,
|
|
362
|
+
staleDays: 30,
|
|
363
|
+
},
|
|
364
|
+
};
|
|
365
|
+
/**
|
|
366
|
+
* Load workspace config from `jeeves.config.json` at a given path.
|
|
367
|
+
*
|
|
368
|
+
* @param workspacePath - Workspace root directory.
|
|
369
|
+
* @returns Parsed config or undefined if missing or invalid.
|
|
370
|
+
*/
|
|
371
|
+
function loadWorkspaceConfig(workspacePath) {
|
|
372
|
+
const configPath = join(workspacePath, WORKSPACE_CONFIG_FILE);
|
|
373
|
+
if (!existsSync(configPath))
|
|
374
|
+
return undefined;
|
|
375
|
+
try {
|
|
376
|
+
const raw = readFileSync(configPath, 'utf-8');
|
|
377
|
+
const parsed = JSON.parse(raw);
|
|
378
|
+
return workspaceConfigSchema.parse(parsed);
|
|
379
|
+
}
|
|
380
|
+
catch (err) {
|
|
381
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
382
|
+
console.warn(`jeeves-core: failed to load ${configPath}: ${msg}`);
|
|
383
|
+
return undefined;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Resolve a config value with four-tier precedence.
|
|
388
|
+
*
|
|
389
|
+
* @param flagValue - CLI flag value (highest priority).
|
|
390
|
+
* @param envValue - Environment variable value.
|
|
391
|
+
* @param fileValue - Value from jeeves.config.json.
|
|
392
|
+
* @param defaultValue - Built-in default (lowest priority).
|
|
393
|
+
* @returns The resolved value with provenance annotation.
|
|
394
|
+
*/
|
|
395
|
+
function resolveConfigValue(flagValue, envValue, fileValue, defaultValue) {
|
|
396
|
+
if (flagValue !== undefined)
|
|
397
|
+
return { value: flagValue, provenance: 'flag' };
|
|
398
|
+
if (envValue !== undefined)
|
|
399
|
+
return { value: envValue, provenance: 'env' };
|
|
400
|
+
if (fileValue !== undefined)
|
|
401
|
+
return { value: fileValue, provenance: 'file' };
|
|
402
|
+
return { value: defaultValue, provenance: 'default' };
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Workspace and config root initialization.
|
|
407
|
+
*
|
|
408
|
+
* @remarks
|
|
409
|
+
* `init()` must be called once before any other core library functions.
|
|
410
|
+
* It caches `workspacePath` and `configRoot` at module level.
|
|
411
|
+
* Core derives all namespaced paths from these values:
|
|
412
|
+
* - `{configRoot}/jeeves-core/` for core config
|
|
413
|
+
* - `{configRoot}/jeeves-{name}/` for each component
|
|
414
|
+
*/
|
|
415
|
+
let state;
|
|
416
|
+
/**
|
|
417
|
+
* Initialize the core library with workspace and config root paths.
|
|
418
|
+
*
|
|
419
|
+
* @param options - Workspace and config root paths.
|
|
420
|
+
*/
|
|
421
|
+
function init(options) {
|
|
422
|
+
state = {
|
|
423
|
+
workspacePath: options.workspacePath,
|
|
424
|
+
configRoot: options.configRoot,
|
|
425
|
+
coreConfigDir: join(options.configRoot, CORE_CONFIG_DIR),
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Get the cached workspace path.
|
|
430
|
+
*
|
|
431
|
+
* @throws Error if `init()` has not been called.
|
|
432
|
+
*/
|
|
433
|
+
function getWorkspacePath() {
|
|
434
|
+
if (!state)
|
|
435
|
+
throw new Error('jeeves-core: init() must be called first');
|
|
436
|
+
return state.workspacePath;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Get the core config directory path.
|
|
440
|
+
*
|
|
441
|
+
* @throws Error if `init()` has not been called.
|
|
442
|
+
*/
|
|
443
|
+
function getCoreConfigDir() {
|
|
444
|
+
if (!state)
|
|
445
|
+
throw new Error('jeeves-core: init() must be called first');
|
|
446
|
+
return state.coreConfigDir;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
var init$1 = /*#__PURE__*/Object.freeze({
|
|
450
|
+
__proto__: null,
|
|
451
|
+
getCoreConfigDir: getCoreConfigDir,
|
|
452
|
+
getWorkspacePath: getWorkspacePath,
|
|
453
|
+
init: init
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Shared CLI defaults and resolution for Jeeves CLI commands.
|
|
458
|
+
*
|
|
459
|
+
* @remarks
|
|
460
|
+
* All root CLI commands share workspace/config-root resolution. Values follow
|
|
461
|
+
* the shared precedence model: flags → env → jeeves.config.json → defaults.
|
|
462
|
+
*/
|
|
463
|
+
/** Default workspace path. */
|
|
464
|
+
const DEFAULT_WORKSPACE = WORKSPACE_CONFIG_DEFAULTS.core.workspace;
|
|
465
|
+
/** Default config root path. */
|
|
466
|
+
const DEFAULT_CONFIG_ROOT = WORKSPACE_CONFIG_DEFAULTS.core.configRoot;
|
|
467
|
+
/** Read a numeric env var or return undefined if missing, empty, or invalid. */
|
|
468
|
+
function readNumericEnv(name) {
|
|
469
|
+
const raw = process.env[name];
|
|
470
|
+
if (raw === undefined || raw.trim() === '')
|
|
471
|
+
return undefined;
|
|
472
|
+
const value = Number(raw);
|
|
473
|
+
return Number.isFinite(value) ? value : undefined;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Resolve shared CLI config using flags, env, file, and defaults.
|
|
477
|
+
*
|
|
478
|
+
* @param opts - Parsed CLI workspace/config-root options.
|
|
479
|
+
* @returns Resolved config tree with provenance on every leaf.
|
|
480
|
+
*/
|
|
481
|
+
function resolveCliConfig(opts) {
|
|
482
|
+
const workspaceSeed = resolveConfigValue(opts.workspace, process.env['JEEVES_WORKSPACE'], undefined, DEFAULT_WORKSPACE);
|
|
483
|
+
const fileConfig = loadWorkspaceConfig(workspaceSeed.value);
|
|
484
|
+
return {
|
|
485
|
+
core: {
|
|
486
|
+
workspace: resolveConfigValue(opts.workspace, process.env['JEEVES_WORKSPACE'], fileConfig?.core?.workspace, DEFAULT_WORKSPACE),
|
|
487
|
+
configRoot: resolveConfigValue(opts.configRoot, process.env['JEEVES_CONFIG_ROOT'], fileConfig?.core?.configRoot, DEFAULT_CONFIG_ROOT),
|
|
488
|
+
gatewayUrl: resolveConfigValue(undefined, process.env['JEEVES_GATEWAY_URL'], fileConfig?.core?.gatewayUrl, WORKSPACE_CONFIG_DEFAULTS.core.gatewayUrl),
|
|
489
|
+
},
|
|
490
|
+
memory: {
|
|
491
|
+
budget: resolveConfigValue(undefined, readNumericEnv('JEEVES_MEMORY_BUDGET'), fileConfig?.memory?.budget, WORKSPACE_CONFIG_DEFAULTS.memory.budget),
|
|
492
|
+
warningThreshold: resolveConfigValue(undefined, readNumericEnv('JEEVES_MEMORY_WARNING_THRESHOLD'), fileConfig?.memory?.warningThreshold, WORKSPACE_CONFIG_DEFAULTS.memory.warningThreshold),
|
|
493
|
+
staleDays: resolveConfigValue(undefined, readNumericEnv('JEEVES_MEMORY_STALE_DAYS'), fileConfig?.memory?.staleDays, WORKSPACE_CONFIG_DEFAULTS.memory.staleDays),
|
|
494
|
+
},
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Initialize core from standard CLI options after resolving shared defaults.
|
|
499
|
+
*
|
|
500
|
+
* @param opts - Parsed Commander options with workspace and configRoot.
|
|
501
|
+
* @returns Resolved CLI config.
|
|
502
|
+
*/
|
|
503
|
+
function initFromOptions(opts) {
|
|
504
|
+
const resolved = resolveCliConfig(opts);
|
|
505
|
+
init({
|
|
506
|
+
workspacePath: resolve(resolved.core.workspace.value),
|
|
507
|
+
configRoot: resolve(resolved.core.configRoot.value),
|
|
508
|
+
});
|
|
509
|
+
return resolved;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* `jeeves config [jsonpath]` — inspect effective shared CLI configuration.
|
|
514
|
+
*
|
|
515
|
+
* @remarks
|
|
516
|
+
* Shows effective values and provenance using the shared precedence model.
|
|
517
|
+
* Optional JSONPath filters the resolved config tree.
|
|
518
|
+
*/
|
|
519
|
+
/** Format a provenance tag. */
|
|
520
|
+
function provenanceTag(provenance) {
|
|
521
|
+
return `[${provenance}]`;
|
|
522
|
+
}
|
|
523
|
+
/** Whether a value is a resolved leaf with provenance. */
|
|
524
|
+
function isResolvedLeaf(value) {
|
|
525
|
+
return (typeof value === 'object' &&
|
|
526
|
+
value !== null &&
|
|
527
|
+
'value' in value &&
|
|
528
|
+
'provenance' in value);
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Build the effective shared CLI config tree.
|
|
532
|
+
*
|
|
533
|
+
* @param opts - Parsed CLI workspace/config-root options.
|
|
534
|
+
* @returns Effective config tree with provenance on each leaf.
|
|
535
|
+
*/
|
|
536
|
+
function buildEffectiveConfig(opts) {
|
|
537
|
+
return initFromOptions(opts);
|
|
538
|
+
}
|
|
539
|
+
/** Print the full effective config tree by walking the resolved structure. */
|
|
540
|
+
function printEffectiveConfig(config) {
|
|
541
|
+
console.log('Effective jeeves.config.json:');
|
|
542
|
+
console.log('');
|
|
543
|
+
for (const [section, entries] of Object.entries(config)) {
|
|
544
|
+
for (const [key, leaf] of Object.entries(entries)) {
|
|
545
|
+
console.log(` ${section}.${key}: ${JSON.stringify(leaf.value)} ${provenanceTag(leaf.provenance)}`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Register the `jeeves config` command.
|
|
551
|
+
*
|
|
552
|
+
* @param program - Root Commander program.
|
|
553
|
+
*/
|
|
554
|
+
function registerConfigCommand(program) {
|
|
555
|
+
program
|
|
556
|
+
.command('config')
|
|
557
|
+
.description('Show effective shared configuration with provenance')
|
|
558
|
+
.argument('[jsonpath]', 'JSONPath filter (e.g. $.core.workspace)')
|
|
559
|
+
.option('-w, --workspace <path>', 'Workspace root path')
|
|
560
|
+
.option('-c, --config-root <path>', 'Platform config root path')
|
|
561
|
+
.action((jsonpath, opts) => {
|
|
562
|
+
const effective = buildEffectiveConfig(opts);
|
|
563
|
+
if (jsonpath) {
|
|
564
|
+
const result = JSONPath({
|
|
565
|
+
path: jsonpath,
|
|
566
|
+
json: effective,
|
|
567
|
+
wrap: false,
|
|
568
|
+
});
|
|
569
|
+
if (result === undefined) {
|
|
570
|
+
console.log(`No config value for: ${jsonpath}`);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
if (isResolvedLeaf(result)) {
|
|
574
|
+
console.log(`${jsonpath}: ${JSON.stringify(result.value)} ${provenanceTag(result.provenance)}`);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
console.log(JSON.stringify(result, null, 2));
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
printEffectiveConfig(effective);
|
|
581
|
+
});
|
|
582
|
+
}
|
|
272
583
|
|
|
273
584
|
/**
|
|
274
585
|
* Dynamic discovery of installed Jeeves component CLIs.
|
|
@@ -345,57 +656,6 @@ function registerComponentProxies(program, componentNames) {
|
|
|
345
656
|
}
|
|
346
657
|
}
|
|
347
658
|
|
|
348
|
-
/**
|
|
349
|
-
* Workspace and config root initialization.
|
|
350
|
-
*
|
|
351
|
-
* @remarks
|
|
352
|
-
* `init()` must be called once before any other core library functions.
|
|
353
|
-
* It caches `workspacePath` and `configRoot` at module level.
|
|
354
|
-
* Core derives all namespaced paths from these values:
|
|
355
|
-
* - `{configRoot}/jeeves-core/` for core config
|
|
356
|
-
* - `{configRoot}/jeeves-{name}/` for each component
|
|
357
|
-
*/
|
|
358
|
-
let state;
|
|
359
|
-
/**
|
|
360
|
-
* Initialize the core library with workspace and config root paths.
|
|
361
|
-
*
|
|
362
|
-
* @param options - Workspace and config root paths.
|
|
363
|
-
*/
|
|
364
|
-
function init(options) {
|
|
365
|
-
state = {
|
|
366
|
-
workspacePath: options.workspacePath,
|
|
367
|
-
configRoot: options.configRoot,
|
|
368
|
-
coreConfigDir: join(options.configRoot, CORE_CONFIG_DIR),
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* Get the cached workspace path.
|
|
373
|
-
*
|
|
374
|
-
* @throws Error if `init()` has not been called.
|
|
375
|
-
*/
|
|
376
|
-
function getWorkspacePath() {
|
|
377
|
-
if (!state)
|
|
378
|
-
throw new Error('jeeves-core: init() must be called first');
|
|
379
|
-
return state.workspacePath;
|
|
380
|
-
}
|
|
381
|
-
/**
|
|
382
|
-
* Get the core config directory path.
|
|
383
|
-
*
|
|
384
|
-
* @throws Error if `init()` has not been called.
|
|
385
|
-
*/
|
|
386
|
-
function getCoreConfigDir() {
|
|
387
|
-
if (!state)
|
|
388
|
-
throw new Error('jeeves-core: init() must be called first');
|
|
389
|
-
return state.coreConfigDir;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
var init$1 = /*#__PURE__*/Object.freeze({
|
|
393
|
-
__proto__: null,
|
|
394
|
-
getCoreConfigDir: getCoreConfigDir,
|
|
395
|
-
getWorkspacePath: getWorkspacePath,
|
|
396
|
-
init: init
|
|
397
|
-
});
|
|
398
|
-
|
|
399
659
|
/**
|
|
400
660
|
* Core configuration schema and resolution.
|
|
401
661
|
*
|
|
@@ -747,7 +1007,7 @@ function parseHeartbeat(fileContent) {
|
|
|
747
1007
|
const userContent = fileContent.slice(0, headingIndex).trim();
|
|
748
1008
|
const sectionContent = fileContent.slice(headingIndex + HEARTBEAT_HEADING.length);
|
|
749
1009
|
const entries = [];
|
|
750
|
-
const h2Re = /^## (jeeves-\S
|
|
1010
|
+
const h2Re = /^## (jeeves-\S+?|MEMORY\.md)(?:: declined)?$/gm;
|
|
751
1011
|
let match;
|
|
752
1012
|
const h2Positions = [];
|
|
753
1013
|
while ((match = h2Re.exec(sectionContent)) !== null) {
|
|
@@ -1550,10 +1810,10 @@ async function updateManagedSection(filePath, content, options = {}) {
|
|
|
1550
1810
|
? `# ${markers.title}\n\n${sectionText}`
|
|
1551
1811
|
: sectionText;
|
|
1552
1812
|
}
|
|
1553
|
-
//
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
//
|
|
1813
|
+
// Build the full managed block
|
|
1814
|
+
const beginLine = formatBeginMarker(markers.begin, coreVersion);
|
|
1815
|
+
const endLine = formatEndMarker(markers.end);
|
|
1816
|
+
// Combine all user content for cleanup detection
|
|
1557
1817
|
const rawUserContent = [parsed.beforeContent, parsed.userContent]
|
|
1558
1818
|
.filter(Boolean)
|
|
1559
1819
|
.join('\n\n')
|
|
@@ -1561,9 +1821,6 @@ async function updateManagedSection(filePath, content, options = {}) {
|
|
|
1561
1821
|
// Strip foreign managed blocks from user content (cross-contamination fix)
|
|
1562
1822
|
const userContent = stripForeignMarkers(rawUserContent, markers);
|
|
1563
1823
|
const cleanupNeeded = needsCleanup(newManagedBody, userContent);
|
|
1564
|
-
// Build the full managed block
|
|
1565
|
-
const beginLine = formatBeginMarker(markers.begin, coreVersion);
|
|
1566
|
-
const endLine = formatEndMarker(markers.end);
|
|
1567
1824
|
const managedParts = [];
|
|
1568
1825
|
managedParts.push(beginLine);
|
|
1569
1826
|
if (cleanupNeeded) {
|
|
@@ -1575,26 +1832,54 @@ async function updateManagedSection(filePath, content, options = {}) {
|
|
|
1575
1832
|
managedParts.push('');
|
|
1576
1833
|
managedParts.push(endLine);
|
|
1577
1834
|
const managedBlock = managedParts.join('\n');
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
//
|
|
1582
|
-
|
|
1583
|
-
|
|
1835
|
+
let newFileContent;
|
|
1836
|
+
if (parsed.found) {
|
|
1837
|
+
// Existing block: update in place — preserve position, don't move.
|
|
1838
|
+
// Strip foreign managed blocks from both content zones (cross-contamination fix).
|
|
1839
|
+
const cleanBefore = stripForeignMarkers(parsed.beforeContent, markers);
|
|
1840
|
+
const cleanAfter = stripForeignMarkers(parsed.userContent, markers);
|
|
1841
|
+
const fileParts = [];
|
|
1842
|
+
if (cleanBefore) {
|
|
1843
|
+
fileParts.push(cleanBefore);
|
|
1584
1844
|
fileParts.push('');
|
|
1585
1845
|
}
|
|
1586
1846
|
fileParts.push(managedBlock);
|
|
1847
|
+
if (cleanAfter) {
|
|
1848
|
+
fileParts.push('');
|
|
1849
|
+
fileParts.push(cleanAfter);
|
|
1850
|
+
}
|
|
1851
|
+
fileParts.push('');
|
|
1852
|
+
newFileContent = fileParts.join('\n');
|
|
1587
1853
|
}
|
|
1588
1854
|
else {
|
|
1589
|
-
//
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1855
|
+
// No existing block: insert new block using the configured position.
|
|
1856
|
+
// Strip orphaned same-type BEGIN markers from user content to prevent
|
|
1857
|
+
// the parser from pairing them with the new END marker on the next cycle.
|
|
1858
|
+
const escapedBegin = markers.begin.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1859
|
+
const orphanedBeginRe = new RegExp(`^<!--\\s*${escapedBegin}(?:\\s*\\|[^>]*)?\\s*(?:—[^>]*)?\\s*-->\\s*$\\n?`, 'gm');
|
|
1860
|
+
const cleanUserContent = userContent
|
|
1861
|
+
.replace(orphanedBeginRe, '')
|
|
1862
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
1863
|
+
.trim();
|
|
1864
|
+
const position = markers.position ?? 'top';
|
|
1865
|
+
const fileParts = [];
|
|
1866
|
+
if (position === 'bottom') {
|
|
1867
|
+
if (cleanUserContent) {
|
|
1868
|
+
fileParts.push(cleanUserContent);
|
|
1869
|
+
fileParts.push('');
|
|
1870
|
+
}
|
|
1871
|
+
fileParts.push(managedBlock);
|
|
1872
|
+
}
|
|
1873
|
+
else {
|
|
1874
|
+
fileParts.push(managedBlock);
|
|
1875
|
+
if (cleanUserContent) {
|
|
1876
|
+
fileParts.push('');
|
|
1877
|
+
fileParts.push(cleanUserContent);
|
|
1878
|
+
}
|
|
1594
1879
|
}
|
|
1880
|
+
fileParts.push('');
|
|
1881
|
+
newFileContent = fileParts.join('\n');
|
|
1595
1882
|
}
|
|
1596
|
-
fileParts.push('');
|
|
1597
|
-
const newFileContent = fileParts.join('\n');
|
|
1598
1883
|
atomicWrite(filePath, newFileContent);
|
|
1599
1884
|
});
|
|
1600
1885
|
}
|
|
@@ -1725,6 +2010,135 @@ async function refreshPlatformContent(options) {
|
|
|
1725
2010
|
copyTemplates(coreConfigDir);
|
|
1726
2011
|
}
|
|
1727
2012
|
|
|
2013
|
+
var skillContent = `---
|
|
2014
|
+
name: jeeves
|
|
2015
|
+
description: Jeeves platform architecture, data flow, component interaction, scripts repo, and coordination knowledge. Use when making architectural decisions, coordinating across components, checking platform health, managing service lifecycle, or working with the scripts repo.
|
|
2016
|
+
---
|
|
2017
|
+
|
|
2018
|
+
# Jeeves Platform Skill
|
|
2019
|
+
|
|
2020
|
+
## Platform Architecture
|
|
2021
|
+
|
|
2022
|
+
Jeeves is a four-component platform coordinated by a shared library (\`@karmaniverous/jeeves\`):
|
|
2023
|
+
|
|
2024
|
+
| Component | Role | Port |
|
|
2025
|
+
|-----------|------|------|
|
|
2026
|
+
| **jeeves-runner** | Execute: scheduled jobs, SQLite state, HTTP API | 1937 |
|
|
2027
|
+
| **jeeves-watcher** | Index: file→Qdrant semantic indexing, inference rules | 1936 |
|
|
2028
|
+
| **jeeves-server** | Present: web UI, file browser, doc render, export | 1934 |
|
|
2029
|
+
| **jeeves-meta** | Distill: LLM synthesis, .meta/ directories, scheduling | 1938 |
|
|
2030
|
+
|
|
2031
|
+
Core (\`@karmaniverous/jeeves\`) is a **library + CLI**, not a service. No port.
|
|
2032
|
+
|
|
2033
|
+
## Data Flow
|
|
2034
|
+
|
|
2035
|
+
\`\`\`
|
|
2036
|
+
Files → Watcher (index) → Qdrant → Meta (synthesize) → .meta/ → Watcher (re-index)
|
|
2037
|
+
↓
|
|
2038
|
+
Runner (schedule) → Scripts → Services ← Server (present) ← Browser
|
|
2039
|
+
\`\`\`
|
|
2040
|
+
|
|
2041
|
+
## Component Interaction
|
|
2042
|
+
|
|
2043
|
+
- **Watcher** indexes files into Qdrant with inference rules and enrichments.
|
|
2044
|
+
- **Meta** reads from Qdrant, synthesizes \`.meta/\` directories, which watcher re-indexes.
|
|
2045
|
+
- **Runner** executes scheduled scripts that may call any service's HTTP API.
|
|
2046
|
+
- **Server** presents files, renders documents, and provides the event gateway.
|
|
2047
|
+
- **Core** provides shared content management (TOOLS.md, SOUL.md, AGENTS.md), service discovery, config resolution, and the component SDK.
|
|
2048
|
+
|
|
2049
|
+
## Service Discovery
|
|
2050
|
+
|
|
2051
|
+
Services find each other via config resolution:
|
|
2052
|
+
1. Component's own config file (\`{configRoot}/jeeves-{name}/config.json\`)
|
|
2053
|
+
2. Core config file (\`{configRoot}/jeeves-core/config.json\`)
|
|
2054
|
+
3. Default port constants
|
|
2055
|
+
|
|
2056
|
+
## Scripts Repo
|
|
2057
|
+
|
|
2058
|
+
Location: \`{configRoot}/jeeves-core/scripts/\`
|
|
2059
|
+
Template: \`@karmaniverous/jeeves-scripts-template\`
|
|
2060
|
+
|
|
2061
|
+
Scripts use utilities from \`@karmaniverous/jeeves\` (general) and \`@karmaniverous/jeeves-runner\` (runner-specific). Any script that could be useful outside runner scheduling belongs in core.
|
|
2062
|
+
|
|
2063
|
+
## Managed Content System
|
|
2064
|
+
|
|
2065
|
+
Core maintains managed sections in workspace files using comment markers:
|
|
2066
|
+
- **TOOLS.md** — Component sections (section mode) + Platform section
|
|
2067
|
+
- **SOUL.md** — Professional discipline and behavioral foundations (block mode)
|
|
2068
|
+
- **AGENTS.md** — Operational protocols and memory architecture (block mode)
|
|
2069
|
+
- **HEARTBEAT.md** — Platform health status (heading-based)
|
|
2070
|
+
|
|
2071
|
+
Managed blocks are stationary after initial insertion. Cleanup detection uses Jaccard similarity on 3-word shingles. Cleanup escalation spawns a gateway session when orphaned content is detected.
|
|
2072
|
+
|
|
2073
|
+
## Workspace Configuration
|
|
2074
|
+
|
|
2075
|
+
\`jeeves.config.json\` at workspace root provides shared defaults:
|
|
2076
|
+
- Precedence: CLI flags → env vars → file → defaults
|
|
2077
|
+
- Namespaced: \`core.*\` (workspace, configRoot, gatewayUrl) and \`memory.*\` (budget, warningThreshold, staleDays)
|
|
2078
|
+
- Inspect with \`jeeves config [jsonpath]\`
|
|
2079
|
+
|
|
2080
|
+
## HEARTBEAT Protocol
|
|
2081
|
+
|
|
2082
|
+
The HEARTBEAT system uses a state machine per component:
|
|
2083
|
+
\`not_installed → deps_missing → config_missing → service_not_installed → service_stopped → healthy\`
|
|
2084
|
+
|
|
2085
|
+
Dependency-aware: hard deps block alerts, soft deps add informational notes. Declined components are tracked via heading suffix.
|
|
2086
|
+
|
|
2087
|
+
## Plugin Lifecycle
|
|
2088
|
+
|
|
2089
|
+
\`\`\`bash
|
|
2090
|
+
# Core install (seed workspace content)
|
|
2091
|
+
npx @karmaniverous/jeeves install
|
|
2092
|
+
|
|
2093
|
+
# Component plugin install
|
|
2094
|
+
npx @karmaniverous/jeeves-{component}-openclaw install
|
|
2095
|
+
|
|
2096
|
+
# Component plugin uninstall
|
|
2097
|
+
npx @karmaniverous/jeeves-{component}-openclaw uninstall
|
|
2098
|
+
|
|
2099
|
+
# Core uninstall (remove managed sections)
|
|
2100
|
+
npx @karmaniverous/jeeves uninstall
|
|
2101
|
+
\`\`\`
|
|
2102
|
+
|
|
2103
|
+
## Memory Hygiene
|
|
2104
|
+
|
|
2105
|
+
MEMORY.md has a character budget (default 20,000). Core tracks:
|
|
2106
|
+
- Character count and usage percentage
|
|
2107
|
+
- Warning at 80% of budget
|
|
2108
|
+
- Stale section candidates (H2 sections whose most recent ISO date exceeds the staleness threshold)
|
|
2109
|
+
- Evergreen sections (no dates) are never flagged
|
|
2110
|
+
|
|
2111
|
+
Review is human/agent-mediated — core does not auto-delete.
|
|
2112
|
+
|
|
2113
|
+
### HEARTBEAT Integration
|
|
2114
|
+
|
|
2115
|
+
Memory hygiene is checked on every \`ComponentWriter\` cycle alongside component health. When budget or staleness thresholds are breached, a \`## MEMORY.md\` alert appears in HEARTBEAT.md under \`# Jeeves Platform Status\`. The alert includes character count, budget usage percentage, and any stale section names. When memory is healthy, the heading is absent — no alert content, no LLM cost on heartbeat polls.
|
|
2116
|
+
|
|
2117
|
+
The \`## MEMORY.md\` heading follows the same declined/active lifecycle as component headings (\`## jeeves-{name}\`). Users can decline memory alerts by changing the heading to \`## MEMORY.md: declined\`.
|
|
2118
|
+
`;
|
|
2119
|
+
|
|
2120
|
+
/**
|
|
2121
|
+
* Skill seeding: write the `jeeves` workspace skill unconditionally.
|
|
2122
|
+
*
|
|
2123
|
+
* @remarks
|
|
2124
|
+
* The skill file is entirely generated — no user-authored content (Decision 48).
|
|
2125
|
+
* Every installer (core CLI and component plugins) writes it unconditionally.
|
|
2126
|
+
* Content is inlined at build time via `rollup-plugin-md.ts`.
|
|
2127
|
+
*/
|
|
2128
|
+
/**
|
|
2129
|
+
* Seed the jeeves workspace skill file.
|
|
2130
|
+
*
|
|
2131
|
+
* @param workspacePath - Workspace root directory.
|
|
2132
|
+
*/
|
|
2133
|
+
function seedSkill(workspacePath) {
|
|
2134
|
+
const skillDir = join(workspacePath, SKILLS_DIR, JEEVES_SKILL_DIR);
|
|
2135
|
+
if (!existsSync(skillDir)) {
|
|
2136
|
+
mkdirSync(skillDir, { recursive: true });
|
|
2137
|
+
}
|
|
2138
|
+
const skillPath = join(skillDir, 'SKILL.md');
|
|
2139
|
+
writeFileSync(skillPath, skillContent, 'utf-8');
|
|
2140
|
+
}
|
|
2141
|
+
|
|
1728
2142
|
/**
|
|
1729
2143
|
* One-shot content seeding used by the CLI install command.
|
|
1730
2144
|
*
|
|
@@ -1783,27 +2197,8 @@ async function seedContent(options) {
|
|
|
1783
2197
|
content: `- ${NOT_INSTALLED_ALERTS[name]}`,
|
|
1784
2198
|
}));
|
|
1785
2199
|
await writeHeartbeatSection(heartbeatPath, entries);
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
/**
|
|
1789
|
-
* Shared CLI defaults and option registration for Jeeves CLI commands.
|
|
1790
|
-
*
|
|
1791
|
-
* @remarks
|
|
1792
|
-
* All three CLI commands (install, uninstall, status) share the same
|
|
1793
|
-
* `--workspace` and `--config-root` options with the same defaults.
|
|
1794
|
-
* This module centralizes them to eliminate duplication.
|
|
1795
|
-
*/
|
|
1796
|
-
/** Default workspace path (current directory). */
|
|
1797
|
-
const DEFAULT_WORKSPACE = '.';
|
|
1798
|
-
/** Default config root path. */
|
|
1799
|
-
const DEFAULT_CONFIG_ROOT = './config';
|
|
1800
|
-
/**
|
|
1801
|
-
* Initialize core from standard CLI options.
|
|
1802
|
-
*
|
|
1803
|
-
* @param opts - Parsed Commander options with workspace and configRoot.
|
|
1804
|
-
*/
|
|
1805
|
-
function initFromOptions(opts) {
|
|
1806
|
-
init({ workspacePath: opts.workspace, configRoot: opts.configRoot });
|
|
2200
|
+
// Seed jeeves workspace skill (Decision 48: overwrite-on-install)
|
|
2201
|
+
seedSkill(getWorkspacePath());
|
|
1807
2202
|
}
|
|
1808
2203
|
|
|
1809
2204
|
/**
|
|
@@ -1824,14 +2219,14 @@ function registerInstallCommand(program) {
|
|
|
1824
2219
|
program
|
|
1825
2220
|
.command('install')
|
|
1826
2221
|
.description('Seed Jeeves platform content into the workspace')
|
|
1827
|
-
.option('-w, --workspace <path>', 'Workspace root path'
|
|
1828
|
-
.option('-c, --config-root <path>', 'Platform config root path'
|
|
2222
|
+
.option('-w, --workspace <path>', 'Workspace root path')
|
|
2223
|
+
.option('-c, --config-root <path>', 'Platform config root path')
|
|
1829
2224
|
.action(async (opts) => {
|
|
2225
|
+
const resolved = initFromOptions(opts);
|
|
1830
2226
|
console.log('Jeeves platform install');
|
|
1831
|
-
console.log(` Workspace: ${
|
|
1832
|
-
console.log(` Config root: ${
|
|
2227
|
+
console.log(` Workspace: ${resolved.core.workspace.value}`);
|
|
2228
|
+
console.log(` Config root: ${resolved.core.configRoot.value}`);
|
|
1833
2229
|
console.log();
|
|
1834
|
-
initFromOptions(opts);
|
|
1835
2230
|
await seedContent({
|
|
1836
2231
|
coreVersion: CORE_VERSION,
|
|
1837
2232
|
});
|
|
@@ -1845,6 +2240,92 @@ function registerInstallCommand(program) {
|
|
|
1845
2240
|
});
|
|
1846
2241
|
}
|
|
1847
2242
|
|
|
2243
|
+
/**
|
|
2244
|
+
* Memory budget accounting and staleness detection for MEMORY.md.
|
|
2245
|
+
*
|
|
2246
|
+
* @remarks
|
|
2247
|
+
* Scans MEMORY.md for ISO date patterns in H2/H3 headings and bullet items.
|
|
2248
|
+
* Reports character count against a configured budget, warning threshold state,
|
|
2249
|
+
* and stale section candidates. Does not auto-delete: review remains
|
|
2250
|
+
* human- or agent-mediated (Decision 42).
|
|
2251
|
+
*/
|
|
2252
|
+
/** ISO date pattern: YYYY-MM-DD. */
|
|
2253
|
+
const ISO_DATE_RE = /\b(\d{4}-\d{2}-\d{2})\b/g;
|
|
2254
|
+
/** H2 heading pattern used to split sections. */
|
|
2255
|
+
const H2_RE = /^## /m;
|
|
2256
|
+
/**
|
|
2257
|
+
* Extract the most recent ISO date from a string.
|
|
2258
|
+
*
|
|
2259
|
+
* @param text - Text to scan for dates.
|
|
2260
|
+
* @returns The most recent date found, or undefined.
|
|
2261
|
+
*/
|
|
2262
|
+
function extractMostRecentDate(text) {
|
|
2263
|
+
const matches = text.match(ISO_DATE_RE);
|
|
2264
|
+
if (!matches)
|
|
2265
|
+
return undefined;
|
|
2266
|
+
let latest;
|
|
2267
|
+
for (const match of matches) {
|
|
2268
|
+
const d = new Date(match + 'T00:00:00Z');
|
|
2269
|
+
if (!Number.isNaN(d.getTime())) {
|
|
2270
|
+
if (!latest || d > latest)
|
|
2271
|
+
latest = d;
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
return latest;
|
|
2275
|
+
}
|
|
2276
|
+
/**
|
|
2277
|
+
* Analyze MEMORY.md for budget and staleness.
|
|
2278
|
+
*
|
|
2279
|
+
* @param options - Analysis configuration.
|
|
2280
|
+
* @returns Memory hygiene result.
|
|
2281
|
+
*/
|
|
2282
|
+
function analyzeMemory(options) {
|
|
2283
|
+
const { workspacePath, budget, warningThreshold, staleDays } = options;
|
|
2284
|
+
const memoryPath = join(workspacePath, WORKSPACE_FILES.memory);
|
|
2285
|
+
if (!existsSync(memoryPath)) {
|
|
2286
|
+
return {
|
|
2287
|
+
exists: false,
|
|
2288
|
+
charCount: 0,
|
|
2289
|
+
budget,
|
|
2290
|
+
usage: 0,
|
|
2291
|
+
warning: false,
|
|
2292
|
+
overBudget: false,
|
|
2293
|
+
staleCandidates: 0,
|
|
2294
|
+
staleSectionNames: [],
|
|
2295
|
+
};
|
|
2296
|
+
}
|
|
2297
|
+
const content = readFileSync(memoryPath, 'utf-8');
|
|
2298
|
+
const charCount = content.length;
|
|
2299
|
+
const usage = budget > 0 ? charCount / budget : charCount > 0 ? Infinity : 0;
|
|
2300
|
+
const warning = usage >= warningThreshold;
|
|
2301
|
+
const overBudget = usage > 1;
|
|
2302
|
+
// Split into H2 sections and scan for staleness
|
|
2303
|
+
const sections = content.split(H2_RE).slice(1); // skip content before first H2
|
|
2304
|
+
const now = Date.now();
|
|
2305
|
+
const thresholdMs = staleDays * 24 * 60 * 60 * 1000;
|
|
2306
|
+
const staleSectionNames = [];
|
|
2307
|
+
for (const section of sections) {
|
|
2308
|
+
const sectionName = section.split('\n')[0]?.trim() ?? '';
|
|
2309
|
+
const recentDate = extractMostRecentDate(section);
|
|
2310
|
+
// Sections without dates are evergreen — never flagged (Decision 47)
|
|
2311
|
+
if (!recentDate)
|
|
2312
|
+
continue;
|
|
2313
|
+
if (now - recentDate.getTime() > thresholdMs) {
|
|
2314
|
+
staleSectionNames.push(sectionName);
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
return {
|
|
2318
|
+
exists: true,
|
|
2319
|
+
charCount,
|
|
2320
|
+
budget,
|
|
2321
|
+
usage,
|
|
2322
|
+
warning,
|
|
2323
|
+
overBudget,
|
|
2324
|
+
staleCandidates: staleSectionNames.length,
|
|
2325
|
+
staleSectionNames,
|
|
2326
|
+
};
|
|
2327
|
+
}
|
|
2328
|
+
|
|
1848
2329
|
/**
|
|
1849
2330
|
* CLI status command: discover components and probe their health.
|
|
1850
2331
|
*
|
|
@@ -1862,12 +2343,12 @@ function registerStatusCommand(program) {
|
|
|
1862
2343
|
program
|
|
1863
2344
|
.command('status')
|
|
1864
2345
|
.description('Discover Jeeves components and probe their health')
|
|
1865
|
-
.option('-w, --workspace <path>', 'Workspace root path'
|
|
1866
|
-
.option('-c, --config-root <path>', 'Platform config root path'
|
|
2346
|
+
.option('-w, --workspace <path>', 'Workspace root path')
|
|
2347
|
+
.option('-c, --config-root <path>', 'Platform config root path')
|
|
1867
2348
|
.option('-t, --timeout <ms>', 'Probe timeout in milliseconds', '3000')
|
|
1868
2349
|
.action(async (opts) => {
|
|
1869
2350
|
const timeoutMs = parseInt(opts.timeout, 10);
|
|
1870
|
-
initFromOptions(opts);
|
|
2351
|
+
const resolved = initFromOptions(opts);
|
|
1871
2352
|
console.log('Jeeves Platform Status');
|
|
1872
2353
|
console.log('='.repeat(60));
|
|
1873
2354
|
console.log();
|
|
@@ -1875,62 +2356,87 @@ function registerStatusCommand(program) {
|
|
|
1875
2356
|
const coreConfigDir = getCoreConfigDir();
|
|
1876
2357
|
const componentVersions = readComponentVersions(coreConfigDir);
|
|
1877
2358
|
const componentNames = Object.keys(componentVersions);
|
|
2359
|
+
let allHealthy = true;
|
|
1878
2360
|
if (componentNames.length === 0) {
|
|
1879
2361
|
console.log('No components registered.');
|
|
1880
|
-
|
|
2362
|
+
console.log();
|
|
1881
2363
|
}
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
2364
|
+
else {
|
|
2365
|
+
const nameWidth = 10;
|
|
2366
|
+
const statusWidth = 30;
|
|
2367
|
+
const versionWidth = 12;
|
|
2368
|
+
const header = [
|
|
2369
|
+
'Component'.padEnd(nameWidth),
|
|
2370
|
+
'Status'.padEnd(statusWidth),
|
|
2371
|
+
'Version'.padEnd(versionWidth),
|
|
2372
|
+
].join(' ');
|
|
2373
|
+
const separator = [
|
|
2374
|
+
'-'.repeat(nameWidth),
|
|
2375
|
+
'-'.repeat(statusWidth),
|
|
2376
|
+
'-'.repeat(versionWidth),
|
|
2377
|
+
].join(' ');
|
|
2378
|
+
console.log(header);
|
|
2379
|
+
console.log(separator);
|
|
2380
|
+
for (const name of componentNames) {
|
|
2381
|
+
let status;
|
|
2382
|
+
let version = '—';
|
|
2383
|
+
try {
|
|
2384
|
+
const url = getServiceUrl(name);
|
|
2385
|
+
const response = await fetchWithTimeout(`${url}/status`, timeoutMs);
|
|
2386
|
+
if (response.ok) {
|
|
2387
|
+
status = '✅ Running';
|
|
2388
|
+
try {
|
|
2389
|
+
const body = await response.json();
|
|
2390
|
+
if (typeof body === 'object' &&
|
|
2391
|
+
body !== null &&
|
|
2392
|
+
'version' in body &&
|
|
2393
|
+
typeof body['version'] ===
|
|
2394
|
+
'string') {
|
|
2395
|
+
version = body['version'];
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
catch {
|
|
2399
|
+
// Non-JSON response — version stays unknown
|
|
1913
2400
|
}
|
|
1914
2401
|
}
|
|
1915
|
-
|
|
1916
|
-
|
|
2402
|
+
else {
|
|
2403
|
+
status = `❌ HTTP ${String(response.status)}`;
|
|
2404
|
+
allHealthy = false;
|
|
1917
2405
|
}
|
|
1918
2406
|
}
|
|
1919
|
-
|
|
1920
|
-
status =
|
|
2407
|
+
catch {
|
|
2408
|
+
status = '❌ Down';
|
|
1921
2409
|
allHealthy = false;
|
|
1922
2410
|
}
|
|
2411
|
+
const row = [
|
|
2412
|
+
name.padEnd(nameWidth),
|
|
2413
|
+
status.padEnd(statusWidth),
|
|
2414
|
+
version.padEnd(versionWidth),
|
|
2415
|
+
].join(' ');
|
|
2416
|
+
console.log(row);
|
|
1923
2417
|
}
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
2418
|
+
console.log();
|
|
2419
|
+
}
|
|
2420
|
+
const memory = analyzeMemory({
|
|
2421
|
+
workspacePath: getWorkspacePath(),
|
|
2422
|
+
budget: resolved.memory.budget.value,
|
|
2423
|
+
warningThreshold: resolved.memory.warningThreshold.value,
|
|
2424
|
+
staleDays: resolved.memory.staleDays.value,
|
|
2425
|
+
});
|
|
2426
|
+
console.log('Memory hygiene');
|
|
2427
|
+
console.log('-'.repeat(60));
|
|
2428
|
+
if (!memory.exists) {
|
|
2429
|
+
console.log('MEMORY.md not found.');
|
|
2430
|
+
}
|
|
2431
|
+
else {
|
|
2432
|
+
const usagePct = Math.round(memory.usage * 100);
|
|
2433
|
+
const status = memory.overBudget
|
|
2434
|
+
? '❌ Over budget'
|
|
2435
|
+
: memory.warning
|
|
2436
|
+
? '⚠ Warning'
|
|
2437
|
+
: '✅ OK';
|
|
2438
|
+
console.log(`Chars: ${String(memory.charCount)} / ${String(memory.budget)} (${String(usagePct)}%) — ${status}`);
|
|
2439
|
+
console.log(`Stale candidates: ${String(memory.staleCandidates)}`);
|
|
1934
2440
|
}
|
|
1935
2441
|
console.log();
|
|
1936
2442
|
if (!allHealthy) {
|
|
@@ -1989,14 +2495,14 @@ function registerUninstallCommand(program) {
|
|
|
1989
2495
|
program
|
|
1990
2496
|
.command('uninstall')
|
|
1991
2497
|
.description('Remove Jeeves managed sections and platform artifacts')
|
|
1992
|
-
.option('-w, --workspace <path>', 'Workspace root path'
|
|
1993
|
-
.option('-c, --config-root <path>', 'Platform config root path'
|
|
2498
|
+
.option('-w, --workspace <path>', 'Workspace root path')
|
|
2499
|
+
.option('-c, --config-root <path>', 'Platform config root path')
|
|
1994
2500
|
.action(async (opts) => {
|
|
2501
|
+
const resolved = initFromOptions(opts);
|
|
1995
2502
|
console.log('Jeeves platform uninstall');
|
|
1996
|
-
console.log(` Workspace: ${
|
|
1997
|
-
console.log(` Config root: ${
|
|
2503
|
+
console.log(` Workspace: ${resolved.core.workspace.value}`);
|
|
2504
|
+
console.log(` Config root: ${resolved.core.configRoot.value}`);
|
|
1998
2505
|
console.log();
|
|
1999
|
-
initFromOptions(opts);
|
|
2000
2506
|
const wsPath = getWorkspacePath();
|
|
2001
2507
|
const coreConfigDir = getCoreConfigDir();
|
|
2002
2508
|
// Remove managed sections from workspace files
|
|
@@ -2064,6 +2570,7 @@ function registerUninstallCommand(program) {
|
|
|
2064
2570
|
* and status subcommands, plus dynamic proxy commands for any installed
|
|
2065
2571
|
* `@karmaniverous/jeeves-*` component packages.
|
|
2066
2572
|
*/
|
|
2573
|
+
checkNodeVersion();
|
|
2067
2574
|
const cli = new Command()
|
|
2068
2575
|
.name('jeeves')
|
|
2069
2576
|
.description('Jeeves AI assistant platform — shared library and CLI')
|
|
@@ -2073,6 +2580,7 @@ const cli = new Command()
|
|
|
2073
2580
|
registerInstallCommand(cli);
|
|
2074
2581
|
registerUninstallCommand(cli);
|
|
2075
2582
|
registerStatusCommand(cli);
|
|
2583
|
+
registerConfigCommand(cli);
|
|
2076
2584
|
// Dynamic discovery: register proxy subcommands for installed components
|
|
2077
2585
|
const discoveredComponents = discoverComponentPackages();
|
|
2078
2586
|
registerComponentProxies(cli, discoveredComponents);
|