@eventmodelers/cli 0.0.17 → 0.0.19

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);
@@ -873,6 +892,25 @@ async function runModeling(kitDir, projectDir) {
873
892
  let stdoutBuffer = '';
874
893
  let pending = null; // one in-flight turn at a time
875
894
 
895
+ // Bare tool names (`→ Bash`, `→ Skill`) tell you nothing happened worth
896
+ // reading — this pulls out the one input field that actually says what the
897
+ // tool did, so the trace is skimmable without the interactive TUI.
898
+ function describeToolUse(block) {
899
+ const input = block.input ?? {};
900
+ switch (block.name) {
901
+ case 'Bash': return `Bash: ${input.command}`;
902
+ case 'Skill': return `Skill: ${input.skill}${input.args ? ` ${input.args}` : ''}`;
903
+ case 'Read': return `Read: ${input.file_path}`;
904
+ case 'Edit': return `Edit: ${input.file_path}`;
905
+ case 'Write': return `Write: ${input.file_path}`;
906
+ case 'Grep': return `Grep: ${input.pattern}`;
907
+ case 'Glob': return `Glob: ${input.pattern}`;
908
+ case 'WebFetch': return `WebFetch: ${input.url}`;
909
+ case 'Agent': return `Agent: ${input.description ?? input.subagent_type ?? ''}`;
910
+ default: return block.name;
911
+ }
912
+ }
913
+
876
914
  // stream-json output loses the normal interactive TUI (tool cards, live diffs) —
877
915
  // this is a plain-text approximation, good enough for a headless/voice runner.
878
916
  function handleLine(line) {
@@ -883,7 +921,7 @@ async function runModeling(kitDir, projectDir) {
883
921
  if (msg.type === 'assistant') {
884
922
  for (const block of msg.message?.content ?? []) {
885
923
  if (block.type === 'text' && block.text) log(block.text);
886
- if (block.type === 'tool_use') log(`→ ${block.name}`);
924
+ if (block.type === 'tool_use') log(`→ ${describeToolUse(block)}`);
887
925
  }
888
926
  return;
889
927
  }
@@ -1001,7 +1039,7 @@ async function runModeling(kitDir, projectDir) {
1001
1039
  const res = await fetch(`${cfg.baseUrl}/api/agent-alive`, {
1002
1040
  method: 'POST',
1003
1041
  headers: { Authorization: `Bearer ${realtimeToken}`, 'Content-Type': 'application/json' },
1004
- 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 }),
1005
1043
  signal: AbortSignal.timeout(10_000),
1006
1044
  });
1007
1045
  if (!res.ok) log(`ping failed: ${res.status}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventmodelers/cli",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
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,7 +276,7 @@ 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
282
  if (!res.ok) console.error(`[agent] Ping failed: ${res.status}`);
@@ -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}`);