@eventmodelers/cli 0.0.18 → 0.0.20

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
@@ -17,6 +17,7 @@ import {
17
17
  import { execSync, spawn } from 'child_process';
18
18
  import { createInterface, emitKeypressEvents, moveCursor, clearScreenDown } from 'readline';
19
19
  import { homedir } from 'os';
20
+ import { randomUUID } from 'crypto';
20
21
 
21
22
  const __filename = fileURLToPath(import.meta.url);
22
23
  const __dirname = dirname(__filename);
@@ -437,6 +438,23 @@ function readJsonSafe(path) {
437
438
  }
438
439
  }
439
440
 
441
+ // Distinguishes this agent process from any other agent pinging the same
442
+ // token/board — e.g. a build-kit and a modeling-kit install in the same project
443
+ // share one root config.json, and without a per-agent id both would upsert the
444
+ // same alive row and race each other. Written to the kit's OWN config.json
445
+ // (inside kitDir, not the shared root one credentials live in) so a build agent
446
+ // and a modeling agent never end up with the same id, and persisted so restarts
447
+ // of this same kit keep reporting under the same identity.
448
+ function ensureAgentId(kitDir) {
449
+ const kitConfigPath = join(kitDir, '.eventmodelers', 'config.json');
450
+ const existing = readJsonSafe(kitConfigPath);
451
+ if (existing.agentId) return existing.agentId;
452
+ const agentId = randomUUID();
453
+ mkdirSync(dirname(kitConfigPath), { recursive: true });
454
+ writeFileSync(kitConfigPath, JSON.stringify({ ...existing, agentId }, null, 2));
455
+ return agentId;
456
+ }
457
+
440
458
  // Hierarchical resolution: a shared config higher up the directory tree (e.g. the
441
459
  // project root's own .eventmodelers/config.json, or ~/.eventmodelers/config.json for
442
460
  // defaults shared across every project) provides the base values — this is where
@@ -828,6 +846,7 @@ async function runModeling(kitDir, projectDir) {
828
846
  const { createClient } = await import('@supabase/supabase-js');
829
847
 
830
848
  const local = loadLocalConfig(kitDir);
849
+ local.agentId = local.agentId ?? ensureAgentId(kitDir);
831
850
  if (!local.token || !local.organizationId) {
832
851
  console.error('❌ --modeling needs platform credentials in .eventmodelers/config.json (token + organizationId) — run `/connect` once or paste your config first.');
833
852
  process.exit(1);
@@ -1020,10 +1039,10 @@ async function runModeling(kitDir, projectDir) {
1020
1039
  const res = await fetch(`${cfg.baseUrl}/api/agent-alive`, {
1021
1040
  method: 'POST',
1022
1041
  headers: { Authorization: `Bearer ${realtimeToken}`, 'Content-Type': 'application/json' },
1023
- body: JSON.stringify({ token: cfg.token, board_id: cfg.boardId, agent_type: 'MODELING' }),
1042
+ body: JSON.stringify({ token: cfg.token, board_id: cfg.boardId, agent_type: 'MODELING', agent_id: cfg.agentId }),
1024
1043
  signal: AbortSignal.timeout(10_000),
1025
1044
  });
1026
- if (!res.ok) log(`ping failed: ${res.status}`);
1045
+ if (!res.ok) log(`ping failed: ${res.status} ${await res.text().catch(() => '')}`);
1027
1046
  } catch (err) {
1028
1047
  log(`ping error: ${err.message}`);
1029
1048
  }
@@ -1215,7 +1234,12 @@ program
1215
1234
  console.error(`❌ --modeling only supports a modeling-kit install (${MODELING_KIT.kitDirName}/) — it subscribes to the org-wide prompt queue, which build-kit stacks don't have. Use \`eventmodelers run\` (optionally with --ollama/--bash) for build-kit's slice-status loop instead.`);
1216
1235
  process.exit(1);
1217
1236
  }
1218
- console.log(`▶ Starting modeling loop (warm Claude process) for ${relative(cwd, kitDir)}...\n`);
1237
+ // Writes to a stdout pipe are asynchronous on POSIX — without waiting for this
1238
+ // write's own flush callback, the heavier synchronous/async work runModeling()
1239
+ // does right after (dynamic imports, config reads) can eat the event-loop tick
1240
+ // this write needed to drain, so a piped watcher sees the ping arrive after
1241
+ // runModeling's own [modeling] log lines instead of before them.
1242
+ await new Promise((res) => process.stdout.write(`▶ Starting modeling loop (warm Claude process) for ${relative(cwd, kitDir)}...\n\n`, res));
1219
1243
  try {
1220
1244
  await runModeling(kitDir, resolve(kitDir, '..'));
1221
1245
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventmodelers/cli",
3
- "version": "0.0.18",
3
+ "version": "0.0.20",
4
4
  "description": "Eventmodelers CLI — real-time Claude agent + skills for Claude Code, for any stack (Node, Supabase, Axon, Cratis, or modeling-only)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -108,6 +108,30 @@ function hasCredentials(cfg) {
108
108
  return !!(cfg.token && cfg.organizationId && cfg.boardId && cfg.baseUrl);
109
109
  }
110
110
 
111
+ // Distinguishes this agent process from any other agent pinging the same
112
+ // token/board — e.g. a build-kit and a modeling-kit install in the same project
113
+ // share one root config.json, and without a per-agent id both would upsert the
114
+ // same alive row and race each other. Written to the kit's OWN config.json
115
+ // (inside kitDir, not the shared root one credentials live in) so a build agent
116
+ // and a modeling agent never end up with the same id, and persisted so restarts
117
+ // of this same kit keep reporting under the same identity.
118
+ function ensureAgentId(kitDir) {
119
+ const kitConfigPath = join(kitDir, '.eventmodelers', 'config.json');
120
+ let existing = {};
121
+ if (existsSync(kitConfigPath)) {
122
+ try {
123
+ existing = JSON.parse(readFileSync(kitConfigPath, 'utf-8'));
124
+ } catch {
125
+ console.warn(`[ralph] Skipping invalid config at ${kitConfigPath}`);
126
+ }
127
+ }
128
+ if (existing.agentId) return existing.agentId;
129
+ const agentId = randomUUID();
130
+ mkdirSync(dirname(kitConfigPath), { recursive: true });
131
+ writeFileSync(kitConfigPath, JSON.stringify({ ...existing, agentId }, null, 2));
132
+ return agentId;
133
+ }
134
+
111
135
  async function fetchPlatformConfig(local) {
112
136
  const remote = await fetchJSON(`${local.baseUrl}/api/config`, {
113
137
  headers: { 'x-token': local.token },
@@ -252,10 +276,10 @@ async function startRealtimeAgent(cfg, kitDir) {
252
276
  const res = await fetch(`${cfg.baseUrl}/api/agent-alive`, {
253
277
  method: 'POST',
254
278
  headers: { Authorization: `Bearer ${realtimeToken}`, 'Content-Type': 'application/json' },
255
- body: JSON.stringify({ token: cfg.token, board_id: cfg.boardId, agent_type: 'BUILD' }),
279
+ body: JSON.stringify({ token: cfg.token, board_id: cfg.boardId, agent_type: 'BUILD', agent_id: cfg.agentId }),
256
280
  signal: AbortSignal.timeout(10_000),
257
281
  });
258
- if (!res.ok) console.error(`[agent] Ping failed: ${res.status}`);
282
+ if (!res.ok) console.error(`[agent] Ping failed: ${res.status} ${await res.text().catch(() => '')}`);
259
283
  } catch (err) {
260
284
  console.error('[agent] Ping error:', err);
261
285
  }
@@ -357,6 +381,7 @@ export { loadLocalConfig, fetchPlatformConfig, retryOn401, startRealtimeAgent };
357
381
 
358
382
  export async function startRalph({ kitDir, projectDir, onTask, onPlannedSlice }) {
359
383
  const local = loadLocalConfig(kitDir);
384
+ local.agentId = local.agentId ?? ensureAgentId(kitDir);
360
385
 
361
386
  console.log(`Ralph — kit: ${kitDir}`);
362
387
  console.log(` project: ${projectDir}`);