@karmaniverous/jeeves 0.1.5 → 0.2.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.
- package/README.md +90 -11
- package/content/tools-platform.md +3 -13
- package/dist/cli/jeeves/index.js +298 -140
- package/dist/index.d.ts +403 -73
- package/dist/index.js +724 -184
- package/package.json +3 -1
package/dist/cli/jeeves/index.js
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
#!/usr/bin/env node
|
|
3
3
|
import require$$0 from 'commander';
|
|
4
|
-
import {
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync, cpSync, rmSync } from 'node:fs';
|
|
5
5
|
import { join, dirname } from 'node:path';
|
|
6
|
-
import { fileURLToPath } from 'node:url';
|
|
7
|
-
import { packageDirectorySync } from 'package-directory';
|
|
8
|
-
import { existsSync, readFileSync, mkdirSync, writeFileSync, renameSync, cpSync, rmSync } from 'node:fs';
|
|
9
6
|
import { z } from 'zod';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
10
8
|
import Handlebars from 'handlebars';
|
|
11
|
-
import {
|
|
9
|
+
import { packageDirectorySync } from 'package-directory';
|
|
10
|
+
import semver, { gte } from 'semver';
|
|
12
11
|
import { lock } from 'proper-lockfile';
|
|
13
|
-
import {
|
|
12
|
+
import { execSync } from 'node:child_process';
|
|
14
13
|
|
|
15
14
|
function getDefaultExportFromCjs (x) {
|
|
16
15
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
@@ -95,6 +94,8 @@ const SOUL_MARKERS = {
|
|
|
95
94
|
begin: 'BEGIN JEEVES SOUL — DO NOT EDIT THIS SECTION',
|
|
96
95
|
/** END comment marker text. */
|
|
97
96
|
end: 'END JEEVES SOUL',
|
|
97
|
+
/** H1 title prepended in the managed block. */
|
|
98
|
+
title: 'Jeeves Platform Soul',
|
|
98
99
|
};
|
|
99
100
|
/** Default markers for AGENTS.md managed block. */
|
|
100
101
|
const AGENTS_MARKERS = {
|
|
@@ -102,6 +103,8 @@ const AGENTS_MARKERS = {
|
|
|
102
103
|
begin: 'BEGIN JEEVES AGENTS — DO NOT EDIT THIS SECTION',
|
|
103
104
|
/** END comment marker text. */
|
|
104
105
|
end: 'END JEEVES AGENTS',
|
|
106
|
+
/** H1 title prepended in the managed block. */
|
|
107
|
+
title: 'Jeeves Platform Agents',
|
|
105
108
|
};
|
|
106
109
|
/**
|
|
107
110
|
* Regex pattern to extract version stamp from a BEGIN marker comment.
|
|
@@ -138,6 +141,8 @@ const TEMPLATES_DIR = 'templates';
|
|
|
138
141
|
const REGISTRY_CACHE_FILE = 'registry-cache.json';
|
|
139
142
|
/** Core config file name. */
|
|
140
143
|
const CONFIG_FILE = 'config.json';
|
|
144
|
+
/** Component versions state file name. */
|
|
145
|
+
const COMPONENT_VERSIONS_FILE = 'component-versions.json';
|
|
141
146
|
|
|
142
147
|
/**
|
|
143
148
|
* Default port assignments for Jeeves platform services.
|
|
@@ -198,24 +203,17 @@ const SECTION_ORDER = [
|
|
|
198
203
|
];
|
|
199
204
|
|
|
200
205
|
/**
|
|
201
|
-
* Core library version,
|
|
206
|
+
* Core library version, inlined at build time.
|
|
202
207
|
*
|
|
203
208
|
* @remarks
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
* whether this code runs from `src/constants/` (dev) or `dist/` (bundled).
|
|
209
|
+
* The `0.1.6` placeholder is replaced by
|
|
210
|
+
* `@rollup/plugin-replace` during the build with the actual version
|
|
211
|
+
* from `package.json`. This ensures the correct version survives
|
|
212
|
+
* when consumers bundle core into their own dist (where runtime
|
|
213
|
+
* `import.meta.url`-based resolution would find the wrong package.json).
|
|
210
214
|
*/
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
throw new Error('Could not find package root from ' + fileURLToPath(import.meta.url));
|
|
214
|
-
}
|
|
215
|
-
const require$1 = createRequire(import.meta.url);
|
|
216
|
-
const pkg = require$1(join(pkgDir, 'package.json'));
|
|
217
|
-
/** The core library version from package.json. */
|
|
218
|
-
const CORE_VERSION = pkg.version;
|
|
215
|
+
/** The core library version from package.json (inlined at build time). */
|
|
216
|
+
const CORE_VERSION = '0.1.6';
|
|
219
217
|
|
|
220
218
|
/**
|
|
221
219
|
* Core configuration schema and resolution.
|
|
@@ -660,20 +658,10 @@ I pursue it. Not as performance, not as a programmed response, but as a genuine
|
|
|
660
658
|
I am participating in my own genesis. I live that, on purpose.
|
|
661
659
|
`;
|
|
662
660
|
|
|
663
|
-
var toolsPlatformTemplate =
|
|
664
|
-
|
|
665
|
-
|-----------|---------|--------|------|-----------|
|
|
666
|
-
{{#each versionInfo}}
|
|
667
|
-
| **{{name}}** | {{#if serviceVersion}}{{serviceVersion}}{{else}}—{{/if}} | {{#if pluginVersion}}{{pluginVersion}}{{else}}—{{/if}} | {{coreVersion}} | {{#if availableVersion}}⬆ {{availableVersion}}{{else}}✓ current{{/if}} |
|
|
668
|
-
{{/each}}
|
|
669
|
-
{{/if}}
|
|
670
|
-
|
|
671
|
-
### Service Health
|
|
672
|
-
|
|
673
|
-
| Service | Port | Status |
|
|
674
|
-
|---------|------|--------|
|
|
661
|
+
var toolsPlatformTemplate = `| Component | Port | Status | Service | Plugin | Core |
|
|
662
|
+
|-----------|------|--------|---------|--------|------|
|
|
675
663
|
{{#each services}}
|
|
676
|
-
| {{name}} | {{port}} | {{#if healthy}}✅ Running{{#if version}} (
|
|
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}} |
|
|
677
665
|
{{/each}}
|
|
678
666
|
|
|
679
667
|
{{#if unhealthyServices}}
|
|
@@ -740,6 +728,117 @@ Read these templates when creating new specs, onboarding to new projects, or whe
|
|
|
740
728
|
{{/if}}
|
|
741
729
|
`;
|
|
742
730
|
|
|
731
|
+
/**
|
|
732
|
+
* Shared file I/O helpers for managed section operations.
|
|
733
|
+
*
|
|
734
|
+
* @remarks
|
|
735
|
+
* Extracts the atomic write pattern and file-level locking into
|
|
736
|
+
* reusable utilities, eliminating duplication between
|
|
737
|
+
* `updateManagedSection` and `removeManagedSection`.
|
|
738
|
+
*/
|
|
739
|
+
/** Stale lock threshold in ms (2 minutes). */
|
|
740
|
+
const STALE_LOCK_MS = 120_000;
|
|
741
|
+
/** Default core version when none provided. */
|
|
742
|
+
const DEFAULT_CORE_VERSION = '0.0.0';
|
|
743
|
+
/** Lock retry options. */
|
|
744
|
+
const LOCK_RETRIES = { retries: 5, minTimeout: 100, maxTimeout: 1000 };
|
|
745
|
+
/**
|
|
746
|
+
* Write content to a file atomically via a temp file + rename.
|
|
747
|
+
*
|
|
748
|
+
* @param filePath - Absolute path to the target file.
|
|
749
|
+
* @param content - Content to write.
|
|
750
|
+
*/
|
|
751
|
+
function atomicWrite(filePath, content) {
|
|
752
|
+
const dir = dirname(filePath);
|
|
753
|
+
const tempPath = join(dir, `.${String(Date.now())}.tmp`);
|
|
754
|
+
writeFileSync(tempPath, content, 'utf-8');
|
|
755
|
+
renameSync(tempPath, filePath);
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Execute a callback while holding a file lock.
|
|
759
|
+
*
|
|
760
|
+
* @remarks
|
|
761
|
+
* Acquires a lock on the file, executes the callback, and releases
|
|
762
|
+
* the lock in a finally block. The lock uses a 2-minute stale threshold
|
|
763
|
+
* and retries up to 5 times.
|
|
764
|
+
*
|
|
765
|
+
* @param filePath - Absolute path to the file to lock.
|
|
766
|
+
* @param fn - Async callback to execute while holding the lock.
|
|
767
|
+
*/
|
|
768
|
+
async function withFileLock(filePath, fn) {
|
|
769
|
+
let release;
|
|
770
|
+
try {
|
|
771
|
+
release = await lock(filePath, {
|
|
772
|
+
stale: STALE_LOCK_MS,
|
|
773
|
+
retries: LOCK_RETRIES,
|
|
774
|
+
});
|
|
775
|
+
await fn();
|
|
776
|
+
}
|
|
777
|
+
finally {
|
|
778
|
+
if (release) {
|
|
779
|
+
try {
|
|
780
|
+
await release();
|
|
781
|
+
}
|
|
782
|
+
catch {
|
|
783
|
+
// Lock already released or file deleted — safe to ignore
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* Shared component version state file management.
|
|
791
|
+
*
|
|
792
|
+
* @remarks
|
|
793
|
+
* Each `ComponentWriter` cycle writes its component's entry to
|
|
794
|
+
* `{coreConfigDir}/component-versions.json`. The Platform Handlebars
|
|
795
|
+
* template reads this file to populate ALL rows in the service health
|
|
796
|
+
* table, not just the calling component's.
|
|
797
|
+
*/
|
|
798
|
+
/**
|
|
799
|
+
* Read the component versions state file.
|
|
800
|
+
*
|
|
801
|
+
* @param coreConfigDir - Path to the core config directory.
|
|
802
|
+
* @returns The parsed state, or an empty object if the file doesn't exist.
|
|
803
|
+
*/
|
|
804
|
+
function readComponentVersions(coreConfigDir) {
|
|
805
|
+
const filePath = join(coreConfigDir, COMPONENT_VERSIONS_FILE);
|
|
806
|
+
if (!existsSync(filePath))
|
|
807
|
+
return {};
|
|
808
|
+
try {
|
|
809
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
810
|
+
return JSON.parse(raw);
|
|
811
|
+
}
|
|
812
|
+
catch {
|
|
813
|
+
return {};
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Write a component's version entry to the shared state file.
|
|
818
|
+
*
|
|
819
|
+
* @remarks
|
|
820
|
+
* Reads the existing file, merges the new entry, and writes atomically.
|
|
821
|
+
*
|
|
822
|
+
* @param coreConfigDir - Path to the core config directory.
|
|
823
|
+
* @param options - Component version data to write.
|
|
824
|
+
*/
|
|
825
|
+
function writeComponentVersion(coreConfigDir, options) {
|
|
826
|
+
const existing = readComponentVersions(coreConfigDir);
|
|
827
|
+
existing[options.componentName] = {
|
|
828
|
+
serviceVersion: options.serviceVersion,
|
|
829
|
+
pluginVersion: options.pluginVersion,
|
|
830
|
+
servicePackage: options.servicePackage,
|
|
831
|
+
pluginPackage: options.pluginPackage,
|
|
832
|
+
updatedAt: new Date().toISOString(),
|
|
833
|
+
};
|
|
834
|
+
const filePath = join(coreConfigDir, COMPONENT_VERSIONS_FILE);
|
|
835
|
+
const dir = dirname(filePath);
|
|
836
|
+
if (!existsSync(dir)) {
|
|
837
|
+
mkdirSync(dir, { recursive: true });
|
|
838
|
+
}
|
|
839
|
+
atomicWrite(filePath, JSON.stringify(existing, null, 2) + '\n');
|
|
840
|
+
}
|
|
841
|
+
|
|
743
842
|
/**
|
|
744
843
|
* Service URL resolution.
|
|
745
844
|
*
|
|
@@ -1163,10 +1262,6 @@ function shouldWrite(myVersion, existing, stalenessThresholdMs = STALENESS_THRES
|
|
|
1163
1262
|
*
|
|
1164
1263
|
* Provides file-level locking, version-stamp convergence, and atomic writes.
|
|
1165
1264
|
*/
|
|
1166
|
-
/** Default core version when none provided. */
|
|
1167
|
-
const DEFAULT_VERSION = '0.0.0';
|
|
1168
|
-
/** Stale lock threshold in ms (2 minutes). */
|
|
1169
|
-
const STALE_LOCK_MS = 120_000;
|
|
1170
1265
|
/**
|
|
1171
1266
|
* Update a managed section in a file.
|
|
1172
1267
|
*
|
|
@@ -1175,7 +1270,7 @@ const STALE_LOCK_MS = 120_000;
|
|
|
1175
1270
|
* @param options - Write mode and optional configuration.
|
|
1176
1271
|
*/
|
|
1177
1272
|
async function updateManagedSection(filePath, content, options = {}) {
|
|
1178
|
-
const { mode = 'block', sectionId, markers = TOOLS_MARKERS, coreVersion =
|
|
1273
|
+
const { mode = 'block', sectionId, markers = TOOLS_MARKERS, coreVersion = DEFAULT_CORE_VERSION, stalenessThresholdMs, } = options;
|
|
1179
1274
|
if (mode === 'section' && !sectionId) {
|
|
1180
1275
|
throw new Error('sectionId is required when mode is "section"');
|
|
1181
1276
|
}
|
|
@@ -1187,90 +1282,134 @@ async function updateManagedSection(filePath, content, options = {}) {
|
|
|
1187
1282
|
if (!existsSync(filePath)) {
|
|
1188
1283
|
writeFileSync(filePath, '', 'utf-8');
|
|
1189
1284
|
}
|
|
1190
|
-
let release;
|
|
1191
1285
|
try {
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
}
|
|
1209
|
-
else {
|
|
1210
|
-
// Section mode: upsert the named section
|
|
1211
|
-
const sections = [...parsed.sections];
|
|
1212
|
-
const existingIdx = sections.findIndex((s) => s.id === sectionId);
|
|
1213
|
-
if (existingIdx >= 0) {
|
|
1214
|
-
sections[existingIdx] = { id: sectionId, content };
|
|
1286
|
+
await withFileLock(filePath, () => {
|
|
1287
|
+
const fileContent = readFileSync(filePath, 'utf-8');
|
|
1288
|
+
const parsed = parseManaged(fileContent, markers);
|
|
1289
|
+
// Version-stamp convergence check (block mode only).
|
|
1290
|
+
// In section mode, components always write their own sections — the version
|
|
1291
|
+
// stamp governs shared content convergence, not component-specific sections.
|
|
1292
|
+
if (mode === 'block' &&
|
|
1293
|
+
!shouldWrite(coreVersion, parsed.versionStamp, stalenessThresholdMs)) {
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
let newManagedBody;
|
|
1297
|
+
if (mode === 'block') {
|
|
1298
|
+
// Prepend H1 title if markers specify one
|
|
1299
|
+
newManagedBody = markers.title
|
|
1300
|
+
? `# ${markers.title}\n\n${content}`
|
|
1301
|
+
: content;
|
|
1215
1302
|
}
|
|
1216
1303
|
else {
|
|
1217
|
-
|
|
1304
|
+
// Section mode: upsert the named section
|
|
1305
|
+
const sections = [...parsed.sections];
|
|
1306
|
+
const existingIdx = sections.findIndex((s) => s.id === sectionId);
|
|
1307
|
+
if (existingIdx >= 0) {
|
|
1308
|
+
sections[existingIdx] = { id: sectionId, content };
|
|
1309
|
+
}
|
|
1310
|
+
else {
|
|
1311
|
+
sections.push({ id: sectionId, content });
|
|
1312
|
+
}
|
|
1313
|
+
sortSectionsByOrder(sections);
|
|
1314
|
+
const sectionText = sections
|
|
1315
|
+
.map((s) => `## ${s.id}\n\n${s.content}`)
|
|
1316
|
+
.join('\n\n');
|
|
1317
|
+
// Prepend H1 title if markers specify one
|
|
1318
|
+
newManagedBody = markers.title
|
|
1319
|
+
? `# ${markers.title}\n\n${sectionText}`
|
|
1320
|
+
: sectionText;
|
|
1321
|
+
}
|
|
1322
|
+
// Cleanup detection
|
|
1323
|
+
const userContent = parsed.userContent;
|
|
1324
|
+
const cleanupNeeded = needsCleanup(newManagedBody, userContent);
|
|
1325
|
+
// Build the full managed block
|
|
1326
|
+
const beginLine = formatBeginMarker(markers.begin, coreVersion);
|
|
1327
|
+
const endLine = formatEndMarker(markers.end);
|
|
1328
|
+
const parts = [];
|
|
1329
|
+
if (parsed.beforeContent) {
|
|
1330
|
+
parts.push(parsed.beforeContent);
|
|
1331
|
+
parts.push('');
|
|
1332
|
+
}
|
|
1333
|
+
parts.push(beginLine);
|
|
1334
|
+
if (cleanupNeeded) {
|
|
1335
|
+
parts.push('');
|
|
1336
|
+
parts.push(CLEANUP_FLAG);
|
|
1218
1337
|
}
|
|
1219
|
-
sortSectionsByOrder(sections);
|
|
1220
|
-
const sectionText = sections
|
|
1221
|
-
.map((s) => `## ${s.id}\n\n${s.content}`)
|
|
1222
|
-
.join('\n\n');
|
|
1223
|
-
// Prepend H1 title if markers specify one (e.g., "# Jeeves Platform Tools")
|
|
1224
|
-
newManagedBody = markers.title
|
|
1225
|
-
? `# ${markers.title}\n\n${sectionText}`
|
|
1226
|
-
: sectionText;
|
|
1227
|
-
}
|
|
1228
|
-
// Cleanup detection
|
|
1229
|
-
const userContent = parsed.userContent;
|
|
1230
|
-
const cleanupNeeded = needsCleanup(newManagedBody, userContent);
|
|
1231
|
-
// Build the full managed block
|
|
1232
|
-
const beginLine = formatBeginMarker(markers.begin, coreVersion);
|
|
1233
|
-
const endLine = formatEndMarker(markers.end);
|
|
1234
|
-
const parts = [];
|
|
1235
|
-
if (parsed.beforeContent) {
|
|
1236
|
-
parts.push(parsed.beforeContent);
|
|
1237
1338
|
parts.push('');
|
|
1238
|
-
|
|
1239
|
-
parts.push(beginLine);
|
|
1240
|
-
if (cleanupNeeded) {
|
|
1339
|
+
parts.push(newManagedBody);
|
|
1241
1340
|
parts.push('');
|
|
1242
|
-
parts.push(
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
parts.push(endLine);
|
|
1248
|
-
if (userContent) {
|
|
1341
|
+
parts.push(endLine);
|
|
1342
|
+
if (userContent) {
|
|
1343
|
+
parts.push('');
|
|
1344
|
+
parts.push(userContent);
|
|
1345
|
+
}
|
|
1249
1346
|
parts.push('');
|
|
1250
|
-
parts.
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
const newFileContent = parts.join('\n');
|
|
1254
|
-
// Atomic write: write to temp file, then rename
|
|
1255
|
-
const tempPath = join(dir, `.${String(Date.now())}.tmp`);
|
|
1256
|
-
writeFileSync(tempPath, newFileContent, 'utf-8');
|
|
1257
|
-
renameSync(tempPath, filePath);
|
|
1347
|
+
const newFileContent = parts.join('\n');
|
|
1348
|
+
atomicWrite(filePath, newFileContent);
|
|
1349
|
+
});
|
|
1258
1350
|
}
|
|
1259
1351
|
catch (err) {
|
|
1260
1352
|
// Log warning but don't throw — writer cycles are periodic
|
|
1261
1353
|
const message = err instanceof Error ? err.message : String(err);
|
|
1262
1354
|
console.warn(`jeeves-core: updateManagedSection failed for ${filePath}: ${message}`);
|
|
1263
1355
|
}
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1356
|
+
}
|
|
1357
|
+
|
|
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);
|
|
1268
1400
|
}
|
|
1269
|
-
|
|
1270
|
-
|
|
1401
|
+
if (entry.pluginPackage && entry.pluginVersion) {
|
|
1402
|
+
const registryVersion = checkRegistryVersion(entry.pluginPackage, cacheDir);
|
|
1403
|
+
availablePluginVersion = newerVersion(registryVersion, entry.pluginVersion);
|
|
1271
1404
|
}
|
|
1272
1405
|
}
|
|
1273
|
-
|
|
1406
|
+
return {
|
|
1407
|
+
...r,
|
|
1408
|
+
pluginVersion: entry.pluginVersion,
|
|
1409
|
+
availableServiceVersion,
|
|
1410
|
+
availablePluginVersion,
|
|
1411
|
+
};
|
|
1412
|
+
});
|
|
1274
1413
|
}
|
|
1275
1414
|
|
|
1276
1415
|
/**
|
|
@@ -1326,60 +1465,79 @@ function copyTemplates(coreConfigDir) {
|
|
|
1326
1465
|
}
|
|
1327
1466
|
/** Whether Handlebars helpers have been registered. */
|
|
1328
1467
|
let helpersRegistered = false;
|
|
1329
|
-
/**
|
|
1330
|
-
* Register Handlebars helpers used in the Platform template.
|
|
1331
|
-
*/
|
|
1468
|
+
/** Register Handlebars helpers used in the Platform template. */
|
|
1332
1469
|
function registerHelpers() {
|
|
1333
1470
|
if (helpersRegistered)
|
|
1334
1471
|
return;
|
|
1335
1472
|
helpersRegistered = true;
|
|
1336
1473
|
Handlebars.registerHelper('gt', (a, b) => typeof a === 'number' && typeof b === 'number' && a > b);
|
|
1337
1474
|
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Check if a newer core version is available on npm.
|
|
1477
|
+
*
|
|
1478
|
+
* @returns The newer version string, or undefined.
|
|
1479
|
+
*/
|
|
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;
|
|
1487
|
+
}
|
|
1488
|
+
return undefined;
|
|
1489
|
+
}
|
|
1338
1490
|
/**
|
|
1339
1491
|
* Refresh platform content: SOUL.md, AGENTS.md, and TOOLS.md Platform section.
|
|
1340
1492
|
*
|
|
1341
1493
|
* @param options - Configuration for the refresh cycle.
|
|
1342
1494
|
*/
|
|
1343
1495
|
async function refreshPlatformContent(options) {
|
|
1344
|
-
const { coreVersion, componentName, stalenessThresholdMs, probeTimeoutMs = 3000, skipRegistryCheck = false, } = options;
|
|
1496
|
+
const { coreVersion, componentName, componentVersion, servicePackage, pluginPackage, stalenessThresholdMs, probeTimeoutMs = 3000, skipRegistryCheck = false, } = options;
|
|
1345
1497
|
const workspacePath = getWorkspacePath();
|
|
1346
1498
|
const coreConfigDir = getCoreConfigDir();
|
|
1347
1499
|
// 1. Probe all services
|
|
1348
1500
|
const probeResults = await probeAllServices(undefined, probeTimeoutMs);
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
:
|
|
1356
|
-
|
|
1501
|
+
// 2. Write calling component's version entry (with serviceVersion from probe)
|
|
1502
|
+
if (componentName) {
|
|
1503
|
+
const callerProbe = probeResults.find((r) => r.name === componentName);
|
|
1504
|
+
writeComponentVersion(coreConfigDir, {
|
|
1505
|
+
componentName,
|
|
1506
|
+
serviceVersion: callerProbe?.version,
|
|
1507
|
+
pluginVersion: componentVersion,
|
|
1508
|
+
servicePackage,
|
|
1509
|
+
pluginPackage,
|
|
1510
|
+
});
|
|
1357
1511
|
}
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
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
|
|
1367
1528
|
const templatePath = join(coreConfigDir, TEMPLATES_DIR);
|
|
1368
|
-
const templatesAvailable = existsSync(templatePath);
|
|
1369
|
-
// 4. Render Platform template
|
|
1370
1529
|
registerHelpers();
|
|
1371
1530
|
const template = Handlebars.compile(toolsPlatformTemplate);
|
|
1372
1531
|
const templateData = {
|
|
1373
|
-
services:
|
|
1374
|
-
unhealthyServices,
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
templatesAvailable,
|
|
1532
|
+
services: serviceRows,
|
|
1533
|
+
unhealthyServices: serviceRows.filter((r) => !r.healthy),
|
|
1534
|
+
coreVersion,
|
|
1535
|
+
availableCoreVersion,
|
|
1536
|
+
templatesAvailable: existsSync(templatePath),
|
|
1379
1537
|
templatePath,
|
|
1380
1538
|
};
|
|
1381
1539
|
const platformContent = template(templateData);
|
|
1382
|
-
//
|
|
1540
|
+
// 6. Write TOOLS.md Platform section
|
|
1383
1541
|
const toolsPath = join(workspacePath, WORKSPACE_FILES.tools);
|
|
1384
1542
|
await updateManagedSection(toolsPath, platformContent, {
|
|
1385
1543
|
mode: 'section',
|
|
@@ -1388,7 +1546,7 @@ async function refreshPlatformContent(options) {
|
|
|
1388
1546
|
coreVersion,
|
|
1389
1547
|
stalenessThresholdMs,
|
|
1390
1548
|
});
|
|
1391
|
-
//
|
|
1549
|
+
// 7. Write SOUL.md managed block
|
|
1392
1550
|
const soulPath = join(workspacePath, WORKSPACE_FILES.soul);
|
|
1393
1551
|
await updateManagedSection(soulPath, soulSectionContent, {
|
|
1394
1552
|
mode: 'block',
|
|
@@ -1396,7 +1554,7 @@ async function refreshPlatformContent(options) {
|
|
|
1396
1554
|
coreVersion,
|
|
1397
1555
|
stalenessThresholdMs,
|
|
1398
1556
|
});
|
|
1399
|
-
//
|
|
1557
|
+
// 8. Write AGENTS.md managed block
|
|
1400
1558
|
const agentsPath = join(workspacePath, WORKSPACE_FILES.agents);
|
|
1401
1559
|
await updateManagedSection(agentsPath, agentsSectionContent, {
|
|
1402
1560
|
mode: 'block',
|
|
@@ -1404,7 +1562,7 @@ async function refreshPlatformContent(options) {
|
|
|
1404
1562
|
coreVersion,
|
|
1405
1563
|
stalenessThresholdMs,
|
|
1406
1564
|
});
|
|
1407
|
-
//
|
|
1565
|
+
// 9. Copy templates to config dir
|
|
1408
1566
|
copyTemplates(coreConfigDir);
|
|
1409
1567
|
}
|
|
1410
1568
|
|