@eventmodelers/cli 2.0.51 → 2.0.53
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/cli.js
CHANGED
|
@@ -42,7 +42,7 @@ import { recordAgentStatus } from './shared/build-kit/lib/slice-run.js';
|
|
|
42
42
|
import { findSliceDir, fingerprint, readBaseline, writeBaseline, compare } from './lib/ui-baseline.js';
|
|
43
43
|
import { defaultAgentName, withInstanceSuffix } from './shared/build-kit/lib/agent-name.js';
|
|
44
44
|
import { acquireAgentId } from './shared/build-kit/lib/agent-lock.js';
|
|
45
|
-
import { readEvents } from './shared/build-kit/lib/slice-events.js';
|
|
45
|
+
import { readEvents, appendEvent, locationOf } from './shared/build-kit/lib/slice-events.js';
|
|
46
46
|
import { findPromptEvent, replayRequest } from './shared/build-kit/lib/prompt-replay.js';
|
|
47
47
|
import { prunableEvents, pruneLog, rememberPruneAnswer, shouldOfferPrune } from './shared/build-kit/lib/event-prune.js';
|
|
48
48
|
import { fold } from './shared/build-kit/lib/slice-state.js';
|
|
@@ -1060,15 +1060,28 @@ async function resolveStack(cliStack) {
|
|
|
1060
1060
|
);
|
|
1061
1061
|
}
|
|
1062
1062
|
|
|
1063
|
+
// Names what is installed now (from the kit's install manifest) and what this run installs.
|
|
1064
|
+
function printInstallVersions(kitDir, stackKey) {
|
|
1065
|
+
const prev = readJsonSafe(join(kitDir, '.eventmodelers', 'install-manifest.json'));
|
|
1066
|
+
if (prev.version) {
|
|
1067
|
+
const same = prev.version === CLI_VERSION;
|
|
1068
|
+
console.log(`Installed: v${prev.version}${prev.stack ? ` (${prev.stack})` : ''}`);
|
|
1069
|
+
console.log(`Installing: v${CLI_VERSION}${stackKey ? ` (${stackKey})` : ''}${same ? ' — same version, files are refreshed' : ''}\n`);
|
|
1070
|
+
} else {
|
|
1071
|
+
console.log(`Installed: nothing yet\nInstalling: v${CLI_VERSION}${stackKey ? ` (${stackKey})` : ''}\n`);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1063
1075
|
async function installStack(stackKey, stackCfg, options = {}) {
|
|
1064
|
-
console.log(
|
|
1065
|
-
console.log(`Using: ${stackKey} (${stackCfg.label})
|
|
1076
|
+
console.log(`🚀 Eventmodelers CLI v${CLI_VERSION}\n`);
|
|
1077
|
+
console.log(`Using: ${stackKey} (${stackCfg.label})`);
|
|
1066
1078
|
|
|
1067
1079
|
// Almost always the cwd. The exception is the global modeling install, which is
|
|
1068
1080
|
// scaffolded into ~/.eventmodelers/kit from wherever `run --standalone` was invoked
|
|
1069
1081
|
// (see ensureGlobalKit) — the mirror image of options.templatesSource below: where we
|
|
1070
1082
|
// install TO, versus where we install FROM.
|
|
1071
1083
|
const targetDir = options.targetDir ? resolve(options.targetDir) : process.cwd();
|
|
1084
|
+
printInstallVersions(join(targetDir, stackCfg.kitDirName), stackKey);
|
|
1072
1085
|
// `init --git <url>` passes a resolved clone dir's templates/ here instead — every
|
|
1073
1086
|
// other input (STACKS, MODELING_KIT) keeps using the built-in path.
|
|
1074
1087
|
const templatesSource = options.templatesSource || join(__dirname, 'stacks', stackKey, 'templates');
|
|
@@ -1487,6 +1500,8 @@ async function configureCredentials({ config, configPath, targetDir, requiredFie
|
|
|
1487
1500
|
// `re-init` for a build kit: re-copy shared/build-kit (ralph.js, the runners, tracing, adapters)
|
|
1488
1501
|
// over the installed kit, then re-run the config steps. Stack templates and skills are untouched.
|
|
1489
1502
|
async function refreshSharedRuntime({ targetDir, kitDir, globalOpts, opts }) {
|
|
1503
|
+
console.log(`🚀 Eventmodelers CLI v${CLI_VERSION}\n`);
|
|
1504
|
+
printInstallVersions(kitDir, readJsonSafe(join(kitDir, '.eventmodelers', 'install-manifest.json')).stack);
|
|
1490
1505
|
console.log(`📦 Refreshing the shared runtime in ${relative(targetDir, kitDir)}/ (stack files untouched)...`);
|
|
1491
1506
|
copyDirContents(join(__dirname, 'shared', 'build-kit'), kitDir);
|
|
1492
1507
|
// The kit is now this CLI's runtime — record that, so the startup "kit is outdated" hint stops.
|
|
@@ -5350,6 +5365,10 @@ program
|
|
|
5350
5365
|
if (!wanted) {
|
|
5351
5366
|
const claimed = Object.entries(fold(readEvents(kitDir)).slices).filter(([, s]) => s.claim);
|
|
5352
5367
|
if (claimed.length !== 1) {
|
|
5368
|
+
if (!claimed.length) {
|
|
5369
|
+
// Where it looked: an agent running from another kit's directory finds no claim in that kit's log.
|
|
5370
|
+
try { appendEvent(kitDir, { type: 'claim-not-found', command: 'build-baseline', claimed: 0, ...locationOf(kitDir, cwd) }, agentEventMeta(cwd, kitDir, command)); } catch { /* best effort */ }
|
|
5371
|
+
}
|
|
5353
5372
|
console.error(`❌ ${claimed.length ? `${claimed.length} slices are claimed` : 'No slice is claimed by the run loop'} — name the slice with --slice.`);
|
|
5354
5373
|
process.exit(1);
|
|
5355
5374
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eventmodelers/cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.53",
|
|
4
4
|
"description": "Eventmodelers CLI — real-time Claude agent + skills for Claude Code, for any stack (Node, Supabase, Axon, OpenCQRS, UmaDB, Kurrent, or modeling-only)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,8 +26,10 @@ function describe(e) {
|
|
|
26
26
|
return [e.slice, e.status ?? ''];
|
|
27
27
|
case 'claimed': {
|
|
28
28
|
const tree = e.cleanTree === true ? 'clean tree' : e.cleanTree === false ? 'dirty tree' : 'not a git tree';
|
|
29
|
-
return [e.slice, `pid ${e.pid}, ${tree}${e.base ? `, ${e.base.branch}@${String(e.base.head).slice(0, 8)}` : ''}${e.ticketNumber ? `, ticket ${e.ticketNumber}` : ''}${e.assignee ? `, assignee ${e.assignee}` : ''}`];
|
|
29
|
+
return [e.slice, `pid ${e.pid}, ${tree}${e.kitDir ? `, kit ${e.kitDir}` : ''}${e.base ? `, ${e.base.branch}@${String(e.base.head).slice(0, 8)}` : ''}${e.ticketNumber ? `, ticket ${e.ticketNumber}` : ''}${e.assignee ? `, assignee ${e.assignee}` : ''}`];
|
|
30
30
|
}
|
|
31
|
+
case 'claim-not-found':
|
|
32
|
+
return [e.slice ?? e.wanted ?? '', `${e.command ?? 'command'} looked in ${e.kitDir ?? '?'} (cwd ${e.cwd ?? '?'}), ${e.claimed ?? 0} claimed there`];
|
|
31
33
|
case 'claim-confirmed':
|
|
32
34
|
return [e.slice, ''];
|
|
33
35
|
case 'released':
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { appendFileSync, closeSync, existsSync, fstatSync, mkdirSync, openSync, readFileSync, readSync, renameSync, writeFileSync } from 'fs';
|
|
9
9
|
import { randomUUID } from 'crypto';
|
|
10
10
|
import { join } from 'path';
|
|
11
|
+
import { homedir } from 'os';
|
|
11
12
|
|
|
12
13
|
export const EVENTS_DIR = 'events';
|
|
13
14
|
export const INDEX_FILE = 'events.log';
|
|
@@ -85,6 +86,14 @@ function nextSeq(kitDir) {
|
|
|
85
86
|
// concurrent processes never interleave however large the event. After a torn index line the new id
|
|
86
87
|
// starts on a fresh line instead of being glued onto the fragment. Every event gets the next sequence number
|
|
87
88
|
// (`seq`), which — not the timestamp — defines the order. Returns the event id.
|
|
89
|
+
// Where a process ran, for events: the kit it works against and its working directory. The log is shared with
|
|
90
|
+
// everyone who can open the board, so the home directory is shown as `~` rather than the user's name.
|
|
91
|
+
export function locationOf(kitDir, cwd = process.cwd()) {
|
|
92
|
+
const home = homedir();
|
|
93
|
+
const tidy = (p) => (home && p && (p === home || p.startsWith(home + '/')) ? `~${p.slice(home.length)}` : p);
|
|
94
|
+
return { kitDir: tidy(kitDir), cwd: tidy(cwd) };
|
|
95
|
+
}
|
|
96
|
+
|
|
88
97
|
export function appendEvent(kitDir, event, meta) {
|
|
89
98
|
const dir = join(kitDir, EVENTS_DIR);
|
|
90
99
|
mkdirSync(dir, { recursive: true });
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// `board` is injected: { setStatus(sliceId, status), claim(sliceId, fromStatus), readStatus(sliceId) → status | null }. null means
|
|
6
6
|
// no board (--local): nothing is pushed or read, the log alone decides.
|
|
7
7
|
|
|
8
|
-
import { appendEvent, readEvents } from './slice-events.js';
|
|
8
|
+
import { appendEvent, readEvents, locationOf } from './slice-events.js';
|
|
9
9
|
import { fold, decideSettle, decideRecovery, statusKey } from './slice-state.js';
|
|
10
10
|
import { adoptAgentEdits, projectStatus } from './slice-projection.js';
|
|
11
11
|
import { discardUnfinishedWork, gitBase } from './slice-git.js';
|
|
@@ -35,17 +35,25 @@ export function isProcessAlive(pid) {
|
|
|
35
35
|
// take the stale file for an edit.
|
|
36
36
|
export function recordAgentStatus({ kitDir, status, sliceId, meta }) {
|
|
37
37
|
const claimed = Object.entries(fold(readEvents(kitDir)).slices).filter(([, s]) => s.claim);
|
|
38
|
+
// A refusal is recorded with where it looked: the usual cause is an agent running from another kit's
|
|
39
|
+
// directory (an app with a root kit and a web/ kit), whose log has no claim for the slice.
|
|
40
|
+
const refuse = (message) => {
|
|
41
|
+
try {
|
|
42
|
+
appendEvent(kitDir, { type: 'claim-not-found', ...(sliceId ? { wanted: sliceId } : {}), claimed: claimed.length, command: 'slice-status', ...locationOf(kitDir) }, meta);
|
|
43
|
+
} catch { /* best effort: the refusal itself is what matters */ }
|
|
44
|
+
throw new Error(message);
|
|
45
|
+
};
|
|
38
46
|
let match;
|
|
39
47
|
if (sliceId) {
|
|
40
48
|
const wanted = sliceId.trim().toLowerCase();
|
|
41
49
|
match = claimed.find(([id, s]) => id === sliceId || s.claim.title?.toLowerCase() === wanted);
|
|
42
|
-
if (!match)
|
|
50
|
+
if (!match) refuse(`slice "${sliceId}" is not claimed by the run loop — only a slice the loop is building has its status recorded here`);
|
|
43
51
|
} else if (claimed.length === 1) {
|
|
44
52
|
match = claimed[0];
|
|
45
53
|
} else if (claimed.length > 1) {
|
|
46
54
|
throw new Error(`${claimed.length} slices are claimed (${claimed.map(([, s]) => s.claim.title).join(', ')}) — name one with --slice`);
|
|
47
55
|
} else {
|
|
48
|
-
|
|
56
|
+
refuse('no slice is claimed by the run loop. For a task from tasks.json, set the status with the update-slice-status skill instead');
|
|
49
57
|
}
|
|
50
58
|
const [id, { claim }] = match;
|
|
51
59
|
recordEvent(kitDir, { type: 'agent-recorded', slice: id, status }, meta);
|
|
@@ -78,7 +86,7 @@ export async function claimSlice({ kitDir, planned, board, meta, cleanTree, pid
|
|
|
78
86
|
}
|
|
79
87
|
}
|
|
80
88
|
const state = recordEvent(kitDir, {
|
|
81
|
-
type: 'claimed', slice: planned.id, ctx: planned.ctx, title: planned.title, pid, base: gitBase(kitDir), cleanTree,
|
|
89
|
+
type: 'claimed', slice: planned.id, ctx: planned.ctx, title: planned.title, pid, base: gitBase(kitDir), cleanTree, ...locationOf(kitDir),
|
|
82
90
|
// What the board says about the slice at build time, when it says anything: both can change between builds.
|
|
83
91
|
...(planned.ticketNumber ? { ticketNumber: planned.ticketNumber } : {}),
|
|
84
92
|
...(planned.assignee ? { assignee: planned.assignee } : {}),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// A slice build's tool calls as events in the slice-state log, so `run --sync` can show them on the board. Any
|
|
2
2
|
// runner that sees its agent's individual tool calls records them through here, so every runner logs the same shape.
|
|
3
3
|
|
|
4
|
-
import { appendEvent } from './slice-events.js';
|
|
4
|
+
import { appendEvent, locationOf } from './slice-events.js';
|
|
5
5
|
|
|
6
6
|
// Only the tool's name is recorded — never its input. A Bash command, a skill's arguments or a path can hold
|
|
7
7
|
// credentials or local details, and this log is shared with everyone who can open the board. Best effort, like the
|
|
@@ -9,7 +9,7 @@ import { appendEvent } from './slice-events.js';
|
|
|
9
9
|
export function recordToolCall({ kitDir, slice, tool, meta }) {
|
|
10
10
|
if (!slice?.sliceId) return;
|
|
11
11
|
try {
|
|
12
|
-
appendEvent(kitDir, { type: 'tool-call', slice: slice.sliceId, tool }, meta);
|
|
12
|
+
appendEvent(kitDir, { type: 'tool-call', slice: slice.sliceId, tool, ...locationOf(kitDir) }, meta);
|
|
13
13
|
} catch (err) {
|
|
14
14
|
console.error(`[ralph] could not record a tool call (${err.message})`);
|
|
15
15
|
}
|