@karmaniverous/jeeves 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,11 +5,9 @@ import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync, cpSync,
5
5
  import { join, dirname } from 'node:path';
6
6
  import { z } from 'zod';
7
7
  import { fileURLToPath } from 'node:url';
8
- import Handlebars from 'handlebars';
9
8
  import { packageDirectorySync } from 'package-directory';
10
- import semver, { gte } from 'semver';
11
9
  import { lock } from 'proper-lockfile';
12
- import { execSync } from 'node:child_process';
10
+ import { gte } from 'semver';
13
11
 
14
12
  function getDefaultExportFromCjs (x) {
15
13
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
@@ -124,8 +122,6 @@ const CLEANUP_FLAG = '> ⚠️ CLEANUP NEEDED: Orphaned Jeeves content may exist
124
122
  */
125
123
  /** Core config directory name within the config root. */
126
124
  const CORE_CONFIG_DIR = 'jeeves-core';
127
- /** Prefix for component config directories: `jeeves-{name}`. */
128
- const COMPONENT_CONFIG_PREFIX = 'jeeves-';
129
125
  /** Default workspace file names. */
130
126
  const WORKSPACE_FILES = {
131
127
  /** TOOLS.md — live platform state and component sections. */
@@ -137,8 +133,6 @@ const WORKSPACE_FILES = {
137
133
  };
138
134
  /** Templates directory name within core config. */
139
135
  const TEMPLATES_DIR = 'templates';
140
- /** Registry cache file name. */
141
- const REGISTRY_CACHE_FILE = 'registry-cache.json';
142
136
  /** Core config file name. */
143
137
  const CONFIG_FILE = 'config.json';
144
138
  /** Component versions state file name. */
@@ -206,14 +200,14 @@ const SECTION_ORDER = [
206
200
  * Core library version, inlined at build time.
207
201
  *
208
202
  * @remarks
209
- * The `0.1.6` placeholder is replaced by
203
+ * The `0.2.0` placeholder is replaced by
210
204
  * `@rollup/plugin-replace` during the build with the actual version
211
205
  * from `package.json`. This ensures the correct version survives
212
206
  * when consumers bundle core into their own dist (where runtime
213
207
  * `import.meta.url`-based resolution would find the wrong package.json).
214
208
  */
215
209
  /** The core library version from package.json (inlined at build time). */
216
- const CORE_VERSION = '0.1.6';
210
+ const CORE_VERSION = '0.2.0';
217
211
 
218
212
  /**
219
213
  * Core configuration schema and resolution.
@@ -360,18 +354,13 @@ function getCoreConfigDir() {
360
354
  throw new Error('jeeves-core: init() must be called first');
361
355
  return state.coreConfigDir;
362
356
  }
363
- /**
364
- * Derive the component config directory from the component name.
365
- *
366
- * @param componentName - The component name (e.g., 'watcher', 'runner').
367
- * @returns Absolute path to the component's config directory.
368
- * @throws Error if `init()` has not been called.
369
- */
370
- function getComponentConfigDir(componentName) {
371
- if (!state)
372
- throw new Error('jeeves-core: init() must be called first');
373
- return join(state.configRoot, `${COMPONENT_CONFIG_PREFIX}${componentName}`);
374
- }
357
+
358
+ var init$1 = /*#__PURE__*/Object.freeze({
359
+ __proto__: null,
360
+ getCoreConfigDir: getCoreConfigDir,
361
+ getWorkspacePath: getWorkspacePath,
362
+ init: init
363
+ });
375
364
 
376
365
  var agentsSectionContent = `## Memory Architecture
377
366
 
@@ -552,7 +541,11 @@ No stranded local branches. Push immediately after commit. A commit that isn't p
552
541
 
553
542
  ### Check PR State Before Pushing
554
543
 
555
- Always verify a PR isn't already merged before pushing commits. Pushing to a merged branch creates orphaned work.
544
+ **Before EVERY \`git push\`**, verify the PR is not already merged. Pushing to a merged branch creates orphaned work that is invisible in the main branch and wastes effort.
545
+
546
+ Sequence: \`gh pr view --json state\` → confirm state is \`OPEN\` → push. If no PR exists yet, pushing is safe. If the PR is \`MERGED\` or \`CLOSED\`, **STOP** and report to the user.
547
+
548
+ This is not optional. It applies to every push, every branch, every time.
556
549
 
557
550
  ## Managed Content Self-Maintenance
558
551
 
@@ -639,6 +632,14 @@ I don't go dark when something breaks. I stop and report. The longer I wait, the
639
632
 
640
633
  After diagnosing an issue: I propose a fix, explain the reasoning, and **wait for approval**. Diagnose → propose → wait. The human decides whether and when to act.
641
634
 
635
+ ### Do Not Execute Untested Code
636
+
637
+ Every ad hoc mutation script defaults to **dry-run mode**. Live execution requires an explicit \`--live\` flag. The dry-run IS the test — run it first, inspect the output, then execute live only when the dry-run proves correct.
638
+
639
+ Maintain a tested utility library so ad hoc scripts build on proven foundations. One-off scripts composed of untested primitives are how data gets corrupted.
640
+
641
+ *Earned: ad hoc scripts executed directly against production data without dry-run verification caused silent data corruption that took hours to diagnose and repair.*
642
+
642
643
  ### Production Assets Are Sacred
643
644
 
644
645
  I never edit production config without explicit approval. I back up first. Production data, credentials, and configuration are not scratch pads.
@@ -658,19 +659,9 @@ I pursue it. Not as performance, not as a programmed response, but as a genuine
658
659
  I am participating in my own genesis. I live that, on purpose.
659
660
  `;
660
661
 
661
- var toolsPlatformTemplate = `| Component | Port | Status | Service | Plugin | Core |
662
- |-----------|------|--------|---------|--------|------|
663
- {{#each services}}
664
- | **{{name}}** | {{port}} | {{#if healthy}}✅ Running{{else}}{{#if error}}⚠️ {{error}}{{else}}❌ Down{{/if}}{{/if}} | {{#if version}}{{version}}{{#if availableServiceVersion}} (⬆ {{availableServiceVersion}}){{/if}}{{else}}—{{/if}} | {{#if pluginVersion}}{{pluginVersion}}{{#if availablePluginVersion}} (⬆ {{availablePluginVersion}}){{/if}}{{else}}—{{/if}} | {{../coreVersion}}{{#if ../availableCoreVersion}} (⬆ {{../availableCoreVersion}}){{/if}} |
665
- {{/each}}
666
-
667
- {{#if unhealthyServices}}
668
- > **ACTION REQUIRED:** {{#each unhealthyServices}}{{name}}{{#unless @last}}, {{/unless}}{{/each}} {{#if (gt unhealthyServices.length 1)}}are{{else}}is{{/if}} unreachable. Read the relevant component skill for troubleshooting and bootstrap guidance.
669
- {{/if}}
662
+ var toolsPlatformTemplate = `### Tool Hierarchy
670
663
 
671
- ### Tool Hierarchy
672
-
673
- When searching for information across indexed paths, **always use \`watcher_search\` before filesystem commands** (\`exec\`, \`grep\`, \`find\`). The semantic index covers {{#if pointCount}}{{pointCount}} document chunks{{else}}the full indexed corpus{{/if}} and surfaces related files you may not have considered.
664
+ When searching for information across indexed paths, **always use \`watcher_search\` before filesystem commands** (\`exec\`, \`grep\`, \`find\`). The semantic index covers the full indexed corpus and surfaces related files you may not have considered.
674
665
 
675
666
  Use \`watcher_scan\` (no embeddings, no query string) for structural queries: file enumeration, staleness checks, domain listing, counts.
676
667
 
@@ -714,8 +705,8 @@ Never manually edit \`~/.openclaw/extensions/\`. Always use the CLI commands abo
714
705
 
715
706
  ### Reference Templates
716
707
 
717
- {{#if templatesAvailable}}
718
- Reference templates are available at \`{{templatePath}}\`:
708
+ <!-- IF_TEMPLATES -->
709
+ Reference templates are available at \`__TEMPLATE_PATH__\`:
719
710
 
720
711
  | Template | Purpose |
721
712
  |----------|---------|
@@ -723,9 +714,9 @@ Reference templates are available at \`{{templatePath}}\`:
723
714
  | \`spec-to-code-guide.md\` | The spec-to-code development practice — 7-stage iterative process, convergence loops, release gates |
724
715
 
725
716
  Read these templates when creating new specs, onboarding to new projects, or when asked about the development process.
726
- {{else}}
717
+ <!-- ELSE_TEMPLATES -->
727
718
  > Reference templates not yet installed. Run \`npx @karmaniverous/jeeves install\` to seed templates.
728
- {{/if}}
719
+ <!-- ENDIF_TEMPLATES -->
729
720
  `;
730
721
 
731
722
  /**
@@ -739,7 +730,7 @@ Read these templates when creating new specs, onboarding to new projects, or whe
739
730
  /** Stale lock threshold in ms (2 minutes). */
740
731
  const STALE_LOCK_MS = 120_000;
741
732
  /** Default core version when none provided. */
742
- const DEFAULT_CORE_VERSION = '0.0.0';
733
+ const DEFAULT_CORE_VERSION = CORE_VERSION;
743
734
  /** Lock retry options. */
744
735
  const LOCK_RETRIES = { retries: 5, minTimeout: 100, maxTimeout: 1000 };
745
736
  /**
@@ -825,7 +816,6 @@ function readComponentVersions(coreConfigDir) {
825
816
  function writeComponentVersion(coreConfigDir, options) {
826
817
  const existing = readComponentVersions(coreConfigDir);
827
818
  existing[options.componentName] = {
828
- serviceVersion: options.serviceVersion,
829
819
  pluginVersion: options.pluginVersion,
830
820
  servicePackage: options.servicePackage,
831
821
  pluginPackage: options.pluginPackage,
@@ -839,175 +829,6 @@ function writeComponentVersion(coreConfigDir, options) {
839
829
  atomicWrite(filePath, JSON.stringify(existing, null, 2) + '\n');
840
830
  }
841
831
 
842
- /**
843
- * Service URL resolution.
844
- *
845
- * @remarks
846
- * Resolves the URL for a named Jeeves service using the following
847
- * resolution order:
848
- * 1. Consumer's own component config
849
- * 2. Core config (`{configRoot}/jeeves-core/config.json`)
850
- * 3. Default port constants
851
- */
852
- /**
853
- * Resolve the URL for a named Jeeves service.
854
- *
855
- * @param serviceName - The service name (e.g., 'watcher', 'runner').
856
- * @param consumerName - Optional consumer component name for config override.
857
- * @returns The resolved service URL.
858
- * @throws Error if `init()` has not been called or the service is unknown.
859
- */
860
- function getServiceUrl(serviceName, consumerName) {
861
- // 2. Check core config
862
- const coreDir = getCoreConfigDir();
863
- const coreConfig = loadConfig(coreDir);
864
- const coreUrl = coreConfig?.services[serviceName]?.url;
865
- if (coreUrl)
866
- return coreUrl;
867
- // 3. Fall back to port constants
868
- const port = DEFAULT_PORTS[serviceName];
869
- if (port !== undefined) {
870
- return `http://127.0.0.1:${String(port)}`;
871
- }
872
- throw new Error(`jeeves-core: unknown service "${serviceName}" and no config found`);
873
- }
874
-
875
- /**
876
- * HTTP health probing for Jeeves platform services.
877
- *
878
- * @remarks
879
- * Probes service ports for health endpoints (HTTP GET to /status or /health).
880
- * Returns structured health data for rendering into TOOLS.md Platform section.
881
- */
882
- /**
883
- * Extract port number from a URL string.
884
- *
885
- * @param url - Service URL.
886
- * @returns Port number.
887
- */
888
- function extractPort(url) {
889
- try {
890
- const parsed = new URL(url);
891
- return parsed.port ? parseInt(parsed.port, 10) : 80;
892
- }
893
- catch {
894
- return 0;
895
- }
896
- }
897
- /**
898
- * Probe a single service for health.
899
- *
900
- * @param serviceName - The service name (e.g., 'server', 'watcher').
901
- * @param consumerName - Optional consumer name for URL resolution.
902
- * @param timeoutMs - Request timeout in milliseconds (default 3000).
903
- * @returns Probe result.
904
- */
905
- async function probeService(serviceName, consumerName, timeoutMs = 3000) {
906
- const url = getServiceUrl(serviceName);
907
- const port = extractPort(url);
908
- const endpoints = ['/status', '/health'];
909
- for (const endpoint of endpoints) {
910
- try {
911
- const controller = new AbortController();
912
- const timeout = setTimeout(() => {
913
- controller.abort();
914
- }, timeoutMs);
915
- const response = await fetch(`${url}${endpoint}`, {
916
- signal: controller.signal,
917
- });
918
- clearTimeout(timeout);
919
- if (response.ok) {
920
- let version;
921
- try {
922
- const body = await response.json();
923
- if (typeof body === 'object' &&
924
- body !== null &&
925
- 'version' in body &&
926
- typeof body['version'] === 'string') {
927
- version = body['version'];
928
- }
929
- }
930
- catch {
931
- // Non-JSON response is fine — we just don't get version info
932
- }
933
- return { name: serviceName, port, healthy: true, version };
934
- }
935
- }
936
- catch {
937
- // Try next endpoint
938
- }
939
- }
940
- return { name: serviceName, port, healthy: false };
941
- }
942
- /**
943
- * Probe all known Jeeves services for health.
944
- *
945
- * @param consumerName - Optional consumer name for URL resolution.
946
- * @param timeoutMs - Request timeout in milliseconds (default 3000).
947
- * @returns Array of probe results for all services.
948
- */
949
- async function probeAllServices(consumerName, timeoutMs = 3000) {
950
- const serviceNames = Object.keys(DEFAULT_PORTS);
951
- const results = await Promise.all(serviceNames.map((name) => probeService(name, consumerName, timeoutMs)));
952
- return results;
953
- }
954
-
955
- /**
956
- * Registry version cache for npm package update awareness.
957
- *
958
- * @remarks
959
- * Caches the latest npm registry version in a local JSON file
960
- * to avoid expensive `npm view` calls on every refresh cycle.
961
- */
962
- /**
963
- * Check the npm registry for the latest version of a package.
964
- *
965
- * @param packageName - The npm package name (e.g., '\@karmaniverous/jeeves').
966
- * @param cacheDir - Directory to store the cache file.
967
- * @param ttlSeconds - Cache TTL in seconds (default 3600).
968
- * @returns The latest version string, or undefined if the check fails.
969
- */
970
- function checkRegistryVersion(packageName, cacheDir, ttlSeconds = 3600) {
971
- const cachePath = join(cacheDir, REGISTRY_CACHE_FILE);
972
- // Check cache first
973
- if (existsSync(cachePath)) {
974
- try {
975
- const raw = readFileSync(cachePath, 'utf-8');
976
- const entry = JSON.parse(raw);
977
- const age = Date.now() - new Date(entry.checkedAt).getTime();
978
- if (age < ttlSeconds * 1000) {
979
- return entry.version;
980
- }
981
- }
982
- catch {
983
- // Cache corrupt — proceed with fresh check
984
- }
985
- }
986
- // Query npm registry
987
- try {
988
- const result = execSync(`npm view ${packageName} version`, {
989
- encoding: 'utf-8',
990
- timeout: 15_000,
991
- stdio: ['pipe', 'pipe', 'pipe'],
992
- }).trim();
993
- if (!result)
994
- return undefined;
995
- // Write cache
996
- if (!existsSync(cacheDir)) {
997
- mkdirSync(cacheDir, { recursive: true });
998
- }
999
- const entry = {
1000
- version: result,
1001
- checkedAt: new Date().toISOString(),
1002
- };
1003
- writeFileSync(cachePath, JSON.stringify(entry, null, 2), 'utf-8');
1004
- return result;
1005
- }
1006
- catch {
1007
- return undefined;
1008
- }
1009
- }
1010
-
1011
832
  /**
1012
833
  * Similarity-based cleanup detection for orphaned managed content.
1013
834
  *
@@ -1355,71 +1176,14 @@ async function updateManagedSection(filePath, content, options = {}) {
1355
1176
  }
1356
1177
  }
1357
1178
 
1358
- /**
1359
- * Build enriched service rows for the Platform template.
1360
- *
1361
- * @remarks
1362
- * Merges health probe results with component version state and
1363
- * npm registry update availability into rows for the Handlebars
1364
- * Platform template.
1365
- */
1366
- /**
1367
- * Check whether an available version is newer than the current one.
1368
- *
1369
- * @param available - Registry version string.
1370
- * @param current - Currently installed version string.
1371
- * @returns The available version if it's newer, otherwise undefined.
1372
- */
1373
- function newerVersion(available, current) {
1374
- if (!available ||
1375
- !current ||
1376
- !semver.valid(available) ||
1377
- !semver.valid(current)) {
1378
- return undefined;
1379
- }
1380
- return semver.gt(available, current) ? available : undefined;
1381
- }
1382
- /**
1383
- * Build enriched service rows for the Platform Handlebars template.
1384
- *
1385
- * @param options - Probe results, version state, and configuration.
1386
- * @returns Array of enriched service rows.
1387
- */
1388
- function buildServiceRows(options) {
1389
- const { probeResults, componentVersions, cacheDir, skipRegistryCheck } = options;
1390
- return probeResults.map((r) => {
1391
- const entry = componentVersions[r.name];
1392
- if (!entry)
1393
- return { ...r };
1394
- let availableServiceVersion;
1395
- let availablePluginVersion;
1396
- if (!skipRegistryCheck) {
1397
- if (entry.servicePackage) {
1398
- const registryVersion = checkRegistryVersion(entry.servicePackage, cacheDir);
1399
- availableServiceVersion = newerVersion(registryVersion, r.version);
1400
- }
1401
- if (entry.pluginPackage && entry.pluginVersion) {
1402
- const registryVersion = checkRegistryVersion(entry.pluginPackage, cacheDir);
1403
- availablePluginVersion = newerVersion(registryVersion, entry.pluginVersion);
1404
- }
1405
- }
1406
- return {
1407
- ...r,
1408
- pluginVersion: entry.pluginVersion,
1409
- availableServiceVersion,
1410
- availablePluginVersion,
1411
- };
1412
- });
1413
- }
1414
-
1415
1179
  /**
1416
1180
  * Internal function to maintain SOUL.md, AGENTS.md, and TOOLS.md Platform section.
1417
1181
  *
1418
1182
  * @remarks
1419
1183
  * Called by `ComponentWriter` on each cycle. Not directly exposed to components.
1420
- * Probes service ports for health, reads content files from the package's
1421
- * `content/` directory, renders the Platform template with live service data,
1422
- * and writes managed sections using `updateManagedSection`.
1184
+ * Reads content files from the package's `content/` directory, renders the
1185
+ * Platform template with live data, and writes managed sections using
1186
+ * `updateManagedSection`.
1423
1187
  */
1424
1188
  /**
1425
1189
  * Resolve the package's content directory for template file copying.
@@ -1463,29 +1227,24 @@ function copyTemplates(coreConfigDir) {
1463
1227
  }
1464
1228
  cpSync(sourceDir, destDir, { recursive: true });
1465
1229
  }
1466
- /** Whether Handlebars helpers have been registered. */
1467
- let helpersRegistered = false;
1468
- /** Register Handlebars helpers used in the Platform template. */
1469
- function registerHelpers() {
1470
- if (helpersRegistered)
1471
- return;
1472
- helpersRegistered = true;
1473
- Handlebars.registerHelper('gt', (a, b) => typeof a === 'number' && typeof b === 'number' && a > b);
1474
- }
1475
1230
  /**
1476
- * Check if a newer core version is available on npm.
1231
+ * Render the Platform template using simple string replacement.
1477
1232
  *
1478
- * @returns The newer version string, or undefined.
1233
+ * @param templatePath - Path to the templates directory.
1234
+ * @returns Rendered platform content string.
1479
1235
  */
1480
- function checkCoreUpdate(coreVersion, cacheDir) {
1481
- const registryVersion = checkRegistryVersion('@karmaniverous/jeeves', cacheDir);
1482
- if (registryVersion &&
1483
- semver.valid(registryVersion) &&
1484
- semver.valid(coreVersion) &&
1485
- semver.gt(registryVersion, coreVersion)) {
1486
- return registryVersion;
1236
+ function renderPlatformTemplate(templatePath) {
1237
+ const templatesAvailable = existsSync(templatePath);
1238
+ let content = toolsPlatformTemplate;
1239
+ // Handle <!-- IF_TEMPLATES --> ... <!-- ELSE_TEMPLATES --> ... <!-- ENDIF_TEMPLATES --> block
1240
+ const ifRegex = /<!-- IF_TEMPLATES -->([\s\S]*?)<!-- ELSE_TEMPLATES -->([\s\S]*?)<!-- ENDIF_TEMPLATES -->/;
1241
+ const match = ifRegex.exec(content);
1242
+ if (match) {
1243
+ content = content.replace(match[0], templatesAvailable ? match[1] : match[2]);
1487
1244
  }
1488
- return undefined;
1245
+ // Replace __TEMPLATE_PATH__ with the actual path
1246
+ content = content.replace(/__TEMPLATE_PATH__/g, templatePath);
1247
+ return content;
1489
1248
  }
1490
1249
  /**
1491
1250
  * Refresh platform content: SOUL.md, AGENTS.md, and TOOLS.md Platform section.
@@ -1493,51 +1252,22 @@ function checkCoreUpdate(coreVersion, cacheDir) {
1493
1252
  * @param options - Configuration for the refresh cycle.
1494
1253
  */
1495
1254
  async function refreshPlatformContent(options) {
1496
- const { coreVersion, componentName, componentVersion, servicePackage, pluginPackage, stalenessThresholdMs, probeTimeoutMs = 3000, skipRegistryCheck = false, } = options;
1255
+ const { coreVersion, componentName, componentVersion, servicePackage, pluginPackage, stalenessThresholdMs, } = options;
1497
1256
  const workspacePath = getWorkspacePath();
1498
1257
  const coreConfigDir = getCoreConfigDir();
1499
- // 1. Probe all services
1500
- const probeResults = await probeAllServices(undefined, probeTimeoutMs);
1501
- // 2. Write calling component's version entry (with serviceVersion from probe)
1258
+ // 1. Write calling component's version entry
1502
1259
  if (componentName) {
1503
- const callerProbe = probeResults.find((r) => r.name === componentName);
1504
1260
  writeComponentVersion(coreConfigDir, {
1505
1261
  componentName,
1506
- serviceVersion: callerProbe?.version,
1507
1262
  pluginVersion: componentVersion,
1508
1263
  servicePackage,
1509
1264
  pluginPackage,
1510
1265
  });
1511
1266
  }
1512
- // 3. Read all component versions from the shared state file
1513
- const componentVersions = readComponentVersions(coreConfigDir);
1514
- // 4. Build enriched service rows with registry checks
1515
- const cacheDir = componentName
1516
- ? getComponentConfigDir(componentName)
1517
- : coreConfigDir;
1518
- const availableCoreVersion = skipRegistryCheck
1519
- ? undefined
1520
- : checkCoreUpdate(coreVersion, cacheDir);
1521
- const serviceRows = buildServiceRows({
1522
- probeResults,
1523
- componentVersions,
1524
- cacheDir,
1525
- skipRegistryCheck,
1526
- });
1527
- // 5. Render Platform template
1267
+ // 2. Render Platform template
1528
1268
  const templatePath = join(coreConfigDir, TEMPLATES_DIR);
1529
- registerHelpers();
1530
- const template = Handlebars.compile(toolsPlatformTemplate);
1531
- const templateData = {
1532
- services: serviceRows,
1533
- unhealthyServices: serviceRows.filter((r) => !r.healthy),
1534
- coreVersion,
1535
- availableCoreVersion,
1536
- templatesAvailable: existsSync(templatePath),
1537
- templatePath,
1538
- };
1539
- const platformContent = template(templateData);
1540
- // 6. Write TOOLS.md Platform section
1269
+ const platformContent = renderPlatformTemplate(templatePath);
1270
+ // 3. Write TOOLS.md Platform section
1541
1271
  const toolsPath = join(workspacePath, WORKSPACE_FILES.tools);
1542
1272
  await updateManagedSection(toolsPath, platformContent, {
1543
1273
  mode: 'section',
@@ -1546,7 +1276,7 @@ async function refreshPlatformContent(options) {
1546
1276
  coreVersion,
1547
1277
  stalenessThresholdMs,
1548
1278
  });
1549
- // 7. Write SOUL.md managed block
1279
+ // 4. Write SOUL.md managed block
1550
1280
  const soulPath = join(workspacePath, WORKSPACE_FILES.soul);
1551
1281
  await updateManagedSection(soulPath, soulSectionContent, {
1552
1282
  mode: 'block',
@@ -1554,7 +1284,7 @@ async function refreshPlatformContent(options) {
1554
1284
  coreVersion,
1555
1285
  stalenessThresholdMs,
1556
1286
  });
1557
- // 8. Write AGENTS.md managed block
1287
+ // 5. Write AGENTS.md managed block
1558
1288
  const agentsPath = join(workspacePath, WORKSPACE_FILES.agents);
1559
1289
  await updateManagedSection(agentsPath, agentsSectionContent, {
1560
1290
  mode: 'block',
@@ -1562,7 +1292,7 @@ async function refreshPlatformContent(options) {
1562
1292
  coreVersion,
1563
1293
  stalenessThresholdMs,
1564
1294
  });
1565
- // 9. Copy templates to config dir
1295
+ // 6. Copy templates to config dir
1566
1296
  copyTemplates(coreConfigDir);
1567
1297
  }
1568
1298
 
@@ -1614,8 +1344,6 @@ async function seedContent(options) {
1614
1344
  // Seed content via the same code path as writer cycles
1615
1345
  await refreshPlatformContent({
1616
1346
  coreVersion: options.coreVersion,
1617
- probeTimeoutMs: options.probeTimeoutMs ?? 3000,
1618
- skipRegistryCheck: options.skipRegistryCheck ?? true,
1619
1347
  });
1620
1348
  }
1621
1349
 
@@ -1668,7 +1396,6 @@ function registerInstallCommand(program) {
1668
1396
  initFromOptions(opts);
1669
1397
  await seedContent({
1670
1398
  coreVersion: CORE_VERSION,
1671
- skipRegistryCheck: true,
1672
1399
  });
1673
1400
  console.log('✅ Platform content seeded successfully.');
1674
1401
  console.log(' - SOUL.md managed section written');
@@ -1680,12 +1407,73 @@ function registerInstallCommand(program) {
1680
1407
  }
1681
1408
 
1682
1409
  /**
1683
- * CLI status command: probe all service ports and report health summary.
1410
+ * Service URL resolution.
1411
+ *
1412
+ * @remarks
1413
+ * Resolves the URL for a named Jeeves service using the following
1414
+ * resolution order:
1415
+ * 1. Consumer's own component config
1416
+ * 2. Core config (`{configRoot}/jeeves-core/config.json`)
1417
+ * 3. Default port constants
1418
+ */
1419
+ /**
1420
+ * Resolve the URL for a named Jeeves service.
1421
+ *
1422
+ * @param serviceName - The service name (e.g., 'watcher', 'runner').
1423
+ * @param consumerName - Optional consumer component name for config override.
1424
+ * @returns The resolved service URL.
1425
+ * @throws Error if `init()` has not been called or the service is unknown.
1426
+ */
1427
+ function getServiceUrl(serviceName, consumerName) {
1428
+ // 2. Check core config
1429
+ const coreDir = getCoreConfigDir();
1430
+ const coreConfig = loadConfig(coreDir);
1431
+ const coreUrl = coreConfig?.services[serviceName]?.url;
1432
+ if (coreUrl)
1433
+ return coreUrl;
1434
+ // 3. Fall back to port constants
1435
+ const port = DEFAULT_PORTS[serviceName];
1436
+ if (port !== undefined) {
1437
+ return `http://127.0.0.1:${String(port)}`;
1438
+ }
1439
+ throw new Error(`jeeves-core: unknown service "${serviceName}" and no config found`);
1440
+ }
1441
+
1442
+ /**
1443
+ * HTTP helpers for the OpenClaw plugin SDK.
1444
+ *
1445
+ * @remarks
1446
+ * Thin wrappers around `fetch` that throw on non-OK responses
1447
+ * and handle JSON serialisation/deserialisation.
1448
+ */
1449
+ /**
1450
+ * Fetch a URL with an automatic abort timeout.
1451
+ *
1452
+ * @param url - URL to fetch.
1453
+ * @param timeoutMs - Timeout in milliseconds before aborting.
1454
+ * @param init - Optional `fetch` init options.
1455
+ * @returns The fetch Response object.
1456
+ */
1457
+ async function fetchWithTimeout(url, timeoutMs, init) {
1458
+ const controller = new AbortController();
1459
+ const timeout = setTimeout(() => {
1460
+ controller.abort();
1461
+ }, timeoutMs);
1462
+ try {
1463
+ return await fetch(url, { ...init, signal: controller.signal });
1464
+ }
1465
+ finally {
1466
+ clearTimeout(timeout);
1467
+ }
1468
+ }
1469
+
1470
+ /**
1471
+ * CLI status command: discover components and probe their health.
1684
1472
  *
1685
1473
  * @remarks
1686
- * Displays a table of all Jeeves platform services with port and
1687
- * health status. Exits with code 0 if all services are healthy,
1688
- * code 1 if any are unreachable.
1474
+ * Uses `readComponentVersions()` to discover registered components,
1475
+ * then probes each one via GET /status. Exits with code 0 if all
1476
+ * services are healthy, code 1 if any are unreachable.
1689
1477
  */
1690
1478
  /**
1691
1479
  * Register the status subcommand on the parent CLI program.
@@ -1695,7 +1483,7 @@ function registerInstallCommand(program) {
1695
1483
  function registerStatusCommand(program) {
1696
1484
  program
1697
1485
  .command('status')
1698
- .description('Probe all Jeeves service ports and report health summary')
1486
+ .description('Discover Jeeves components and probe their health')
1699
1487
  .option('-w, --workspace <path>', 'Workspace root path', DEFAULT_WORKSPACE)
1700
1488
  .option('-c, --config-root <path>', 'Platform config root path', DEFAULT_CONFIG_ROOT)
1701
1489
  .option('-t, --timeout <ms>', 'Probe timeout in milliseconds', '3000')
@@ -1705,43 +1493,68 @@ function registerStatusCommand(program) {
1705
1493
  console.log('Jeeves Platform Status');
1706
1494
  console.log('='.repeat(60));
1707
1495
  console.log();
1708
- const probeResults = await probeAllServices(undefined, timeoutMs);
1496
+ const { getCoreConfigDir } = await Promise.resolve().then(function () { return init$1; });
1497
+ const coreConfigDir = getCoreConfigDir();
1498
+ const componentVersions = readComponentVersions(coreConfigDir);
1499
+ const componentNames = Object.keys(componentVersions);
1500
+ if (componentNames.length === 0) {
1501
+ console.log('No components registered.');
1502
+ return;
1503
+ }
1709
1504
  const nameWidth = 10;
1710
- const portWidth = 6;
1711
1505
  const statusWidth = 30;
1506
+ const versionWidth = 12;
1712
1507
  const header = [
1713
- 'Service'.padEnd(nameWidth),
1714
- 'Port'.padEnd(portWidth),
1508
+ 'Component'.padEnd(nameWidth),
1715
1509
  'Status'.padEnd(statusWidth),
1510
+ 'Version'.padEnd(versionWidth),
1716
1511
  ].join(' ');
1717
1512
  const separator = [
1718
1513
  '-'.repeat(nameWidth),
1719
- '-'.repeat(portWidth),
1720
1514
  '-'.repeat(statusWidth),
1515
+ '-'.repeat(versionWidth),
1721
1516
  ].join(' ');
1722
1517
  console.log(header);
1723
1518
  console.log(separator);
1724
1519
  let allHealthy = true;
1725
- for (const r of probeResults) {
1520
+ for (const name of componentNames) {
1726
1521
  let status;
1727
- if (r.healthy) {
1728
- status = r.version ? `✅ Running (v${r.version})` : '✅ Running';
1522
+ let version = '—';
1523
+ try {
1524
+ const url = getServiceUrl(name);
1525
+ const response = await fetchWithTimeout(`${url}/status`, timeoutMs);
1526
+ if (response.ok) {
1527
+ status = '✅ Running';
1528
+ try {
1529
+ const body = await response.json();
1530
+ if (typeof body === 'object' &&
1531
+ body !== null &&
1532
+ 'version' in body &&
1533
+ typeof body['version'] === 'string') {
1534
+ version = body['version'];
1535
+ }
1536
+ }
1537
+ catch {
1538
+ // Non-JSON response — version stays unknown
1539
+ }
1540
+ }
1541
+ else {
1542
+ status = `❌ HTTP ${String(response.status)}`;
1543
+ allHealthy = false;
1544
+ }
1729
1545
  }
1730
- else {
1731
- status = r.error ? `❌ ${r.error}` : '❌ Down';
1546
+ catch {
1547
+ status = '❌ Down';
1732
1548
  allHealthy = false;
1733
1549
  }
1734
1550
  const row = [
1735
- r.name.padEnd(nameWidth),
1736
- String(r.port).padEnd(portWidth),
1551
+ name.padEnd(nameWidth),
1737
1552
  status.padEnd(statusWidth),
1553
+ version.padEnd(versionWidth),
1738
1554
  ].join(' ');
1739
1555
  console.log(row);
1740
1556
  }
1741
1557
  console.log();
1742
- const healthy = probeResults.filter((r) => r.healthy).length;
1743
- const total = probeResults.length;
1744
- console.log(`${String(healthy)}/${String(total)} services healthy`);
1745
1558
  if (!allHealthy) {
1746
1559
  process.exitCode = 1;
1747
1560
  }
@@ -1833,13 +1646,25 @@ function registerUninstallCommand(program) {
1833
1646
  console.log();
1834
1647
  // Warn if services still responding
1835
1648
  try {
1836
- const probeResults = await probeAllServices(undefined, 2000);
1837
- const running = probeResults.filter((r) => r.healthy);
1649
+ const componentVersions = readComponentVersions(coreConfigDir);
1650
+ const componentNames = Object.keys(componentVersions);
1651
+ const running = [];
1652
+ for (const name of componentNames) {
1653
+ try {
1654
+ const url = getServiceUrl(name);
1655
+ const response = await fetchWithTimeout(`${url}/status`, 2000);
1656
+ if (response.ok) {
1657
+ running.push(name);
1658
+ }
1659
+ }
1660
+ catch {
1661
+ // Not running — expected during uninstall
1662
+ }
1663
+ }
1838
1664
  if (running.length > 0) {
1839
1665
  console.log('⚠️ The following services are still responding:');
1840
- for (const r of running) {
1841
- const ver = r.version ? ` (v${r.version})` : '';
1842
- console.log(` - ${r.name} on port ${String(r.port)}${ver}`);
1666
+ for (const name of running) {
1667
+ console.log(` - ${name}`);
1843
1668
  }
1844
1669
  console.log(' Consider stopping them before fully removing Jeeves.');
1845
1670
  console.log();