@eventmodelers/cli 0.0.26 → 0.0.28
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 +4 -4
- package/cli.js +24 -11
- package/lib/fetch.js +30 -42
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -225,9 +225,9 @@ npx @eventmodelers/cli run --ollama # same, via local Ollama (ra
|
|
|
225
225
|
npx @eventmodelers/cli run --bash # bash-only loop, no realtime (ralph.sh)
|
|
226
226
|
npx @eventmodelers/cli listen # start the code-export listener (code-export.mjs) from the installed kit dir
|
|
227
227
|
npx @eventmodelers/cli listen --port 4000 # same, on a different port
|
|
228
|
-
npx @eventmodelers/cli fetch
|
|
229
|
-
npx @eventmodelers/cli fetch --slice-id <id>
|
|
230
|
-
npx @eventmodelers/cli fetch --slice-title <title>
|
|
228
|
+
npx @eventmodelers/cli fetch --context <name> # pull full slice detail for one context on the board into <kit-dir>/.slices/ (project root if no kit, or a modeling-kit, is installed)
|
|
229
|
+
npx @eventmodelers/cli fetch --context <name> --slice-id <id> # same, then print just that slice
|
|
230
|
+
npx @eventmodelers/cli fetch --context <name> --slice-title <title> # same, then print just the slice matching this title
|
|
231
231
|
npx @eventmodelers/cli stacks # list available stacks
|
|
232
232
|
npx @eventmodelers/cli status # check what's installed
|
|
233
233
|
npx @eventmodelers/cli config # print the fully resolved config (file + env), token masked
|
|
@@ -238,7 +238,7 @@ npx @eventmodelers/cli uninstall # remove everything init/ini
|
|
|
238
238
|
|
|
239
239
|
`listen` is the same kind of dispatcher, but for `<kit-dir>/code-export.mjs` — a local HTTP server (port 3001 by default) that the eventmodelers board UI posts slice/screen data to, which then gets written under `<kit-dir>/.slices/`.
|
|
240
240
|
|
|
241
|
-
`fetch` is the pull-based counterpart to `listen`: instead of waiting for the board UI to push data to a running listener, it
|
|
241
|
+
`fetch` is the pull-based counterpart to `listen`: instead of waiting for the board UI to push data to a running listener, it calls `slicedata?contextName=<name>` for the required `--context` (full slice detail — commands/events/readmodels/screens/processors/specifications/comments), and writes the same `.slices/<context>/<slice>/slice.json`, `index.json`, and `context.json` layout — useful in CI or any context where nothing is listening on a port. It does not fetch screen images (those only arrive via `listen`'s push). Unlike every other command, `fetch` also works with no kit installed at all — it only needs credentials, not kit-specific files — and, unique to modeling-kit, writes `.slices/` to the project root instead of nesting it under `.agent-modeling-kit/`, since nothing reads it from there (modeling-kit has no `code-export.mjs`/`listen`). If credentials are missing, it prompts the same way `init-config` does. `--slice-id`/`--slice-title` still fetch and persist the whole context, then just print the one slice you asked about.
|
|
242
242
|
|
|
243
243
|
### Uninstall
|
|
244
244
|
|
package/cli.js
CHANGED
|
@@ -734,8 +734,15 @@ async function configureCredentials({ config, configPath, targetDir, requiredFie
|
|
|
734
734
|
}
|
|
735
735
|
|
|
736
736
|
const stillMissing = force || requiredFields.some((f) => !config[f]);
|
|
737
|
+
// Sole gate on the persist step below — 'instructions'/'skip' and a paste that
|
|
738
|
+
// couldn't be parsed all explicitly tell the user nothing was saved, so the
|
|
739
|
+
// final write must not run for them (it used to run unconditionally, silently
|
|
740
|
+
// writing out whatever `config` happened to be — `{}` on a first run — which
|
|
741
|
+
// contradicted those messages and left behind a bogus config.json).
|
|
742
|
+
let skipWrite = false;
|
|
737
743
|
if (stillMissing && print) {
|
|
738
744
|
console.log('\n ℹ️ --print — skipping credential prompt, missing fields must be set via flags, EVENTMODELERS_* env vars, or config.json');
|
|
745
|
+
skipWrite = true;
|
|
739
746
|
} else if (stillMissing) {
|
|
740
747
|
const choice = await selectPrompt('How do you want to configure credentials?', [
|
|
741
748
|
{ label: 'Paste values copied from app.eventmodelers.ai/account', value: 'paste' },
|
|
@@ -751,12 +758,10 @@ async function configureCredentials({ config, configPath, targetDir, requiredFie
|
|
|
751
758
|
const parsed = parseCredentialsPaste(pasted, requiredFields);
|
|
752
759
|
if (parsed) {
|
|
753
760
|
config = { ...config, ...parsed };
|
|
754
|
-
if (!config.baseUrl) config.baseUrl = DEFAULT_BASE_URL;
|
|
755
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
756
|
-
console.log(`\n ✓ Saved to ${relative(targetDir, configPath)}`);
|
|
757
761
|
} else {
|
|
758
762
|
console.log(`\n ⚠️ Couldn't make sense of that paste — nothing was saved.`);
|
|
759
763
|
console.log(` Paste it into ${relative(targetDir, configPath)} yourself, or use /connect later.`);
|
|
764
|
+
skipWrite = true;
|
|
760
765
|
}
|
|
761
766
|
} else if (choice === 'manual') {
|
|
762
767
|
console.log('\n🔑 Enter your Eventmodelers credentials:\n');
|
|
@@ -766,9 +771,6 @@ async function configureCredentials({ config, configPath, targetDir, requiredFie
|
|
|
766
771
|
const boardId = await prompt(` Board ID${boardIdOptional ? ' (optional)' : ''}: `);
|
|
767
772
|
if (boardId) config.boardId = boardId;
|
|
768
773
|
config.token = await prompt(' Token: ');
|
|
769
|
-
if (!config.baseUrl) config.baseUrl = DEFAULT_BASE_URL;
|
|
770
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
771
|
-
console.log(`\n ✓ Credentials saved to ${relative(targetDir, configPath)}`);
|
|
772
774
|
} else if (choice === 'instructions') {
|
|
773
775
|
console.log(`\n Paste your credentials into:\n`);
|
|
774
776
|
console.log(` ${configPath}`);
|
|
@@ -778,8 +780,10 @@ async function configureCredentials({ config, configPath, targetDir, requiredFie
|
|
|
778
780
|
const sample = ` {\n "token": "...",\n "boardId": "...",\n "organizationId": "...",\n "baseUrl": "https://api.eventmodelers.ai"\n }\n`;
|
|
779
781
|
console.log(sample);
|
|
780
782
|
console.log(' Then re-run this installer, or just run the agent afterwards.\n');
|
|
783
|
+
skipWrite = true;
|
|
781
784
|
} else {
|
|
782
785
|
console.log('\n ℹ️ Skipped — use /connect in Claude Code to add credentials later');
|
|
786
|
+
skipWrite = true;
|
|
783
787
|
}
|
|
784
788
|
} else {
|
|
785
789
|
console.log('\n ✓ Config already present — skipping credential prompt');
|
|
@@ -791,8 +795,10 @@ async function configureCredentials({ config, configPath, targetDir, requiredFie
|
|
|
791
795
|
config.baseUrl = DEFAULT_BASE_URL;
|
|
792
796
|
}
|
|
793
797
|
|
|
794
|
-
|
|
795
|
-
|
|
798
|
+
if (!skipWrite) {
|
|
799
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
800
|
+
console.log(`\n ✓ Saved to ${relative(targetDir, configPath)}`);
|
|
801
|
+
}
|
|
796
802
|
return config;
|
|
797
803
|
}
|
|
798
804
|
|
|
@@ -1457,12 +1463,19 @@ program
|
|
|
1457
1463
|
|
|
1458
1464
|
program
|
|
1459
1465
|
.command('fetch')
|
|
1460
|
-
.description('Pull full slice detail
|
|
1466
|
+
.description('Pull full slice detail for one context on the board via the slicedata API and write it into .slices/ — the pull-based counterpart to `listen`, without screen images')
|
|
1467
|
+
.requiredOption('--context <name>', 'Name of the MODEL_CONTEXT to fetch')
|
|
1461
1468
|
.option('--slice-id <id>', 'After fetching, print just the slice with this id')
|
|
1462
1469
|
.option('--slice-title <title>', 'After fetching, print just the slice with this title (case-insensitive)')
|
|
1463
1470
|
.action(async (opts, command) => {
|
|
1464
1471
|
const cwd = process.cwd();
|
|
1465
1472
|
const kitDir = findInstalledKitDir(cwd);
|
|
1473
|
+
// modeling-kit has no code-export.mjs (useShared:false, no `listen`) and no
|
|
1474
|
+
// skill reads a nested .agent-modeling-kit/.slices/ — so nothing expects fetch's
|
|
1475
|
+
// output there either. Every other kit dir does have a code-export.mjs that
|
|
1476
|
+
// hardcodes its .slices/ next to itself, so those still nest to stay
|
|
1477
|
+
// interchangeable with what `listen` produces.
|
|
1478
|
+
const slicesKitDir = kitDir?.endsWith(MODELING_KIT.kitDirName) ? null : kitDir;
|
|
1466
1479
|
const globalOpts = command.optsWithGlobals();
|
|
1467
1480
|
const explicitConfig = globalOpts.config;
|
|
1468
1481
|
const effective = loadEffectiveConfig(cwd, kitDir, explicitConfig);
|
|
@@ -1496,7 +1509,7 @@ program
|
|
|
1496
1509
|
if (requiredFields.some((f) => !cfg[f])) await promptForCredentials();
|
|
1497
1510
|
|
|
1498
1511
|
try {
|
|
1499
|
-
await runFetch({ cwd, kitDir, cfg, opts });
|
|
1512
|
+
await runFetch({ cwd, kitDir: slicesKitDir, cfg, opts });
|
|
1500
1513
|
} catch (err) {
|
|
1501
1514
|
if (!(err instanceof FetchAuthError)) throw err;
|
|
1502
1515
|
// Present but wrong, not missing — the connect skill's Step 4 (Verify) treats
|
|
@@ -1506,7 +1519,7 @@ program
|
|
|
1506
1519
|
if (err.status === 404) delete cfg.boardId;
|
|
1507
1520
|
else delete cfg.token;
|
|
1508
1521
|
await promptForCredentials();
|
|
1509
|
-
await runFetch({ cwd, kitDir, cfg, opts });
|
|
1522
|
+
await runFetch({ cwd, kitDir: slicesKitDir, cfg, opts });
|
|
1510
1523
|
}
|
|
1511
1524
|
});
|
|
1512
1525
|
|
package/lib/fetch.js
CHANGED
|
@@ -45,11 +45,13 @@ function sliceFolderName(title) {
|
|
|
45
45
|
return (title ?? '').replaceAll(' ', '').replaceAll('slice:', '').toLowerCase();
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
// Pulls full slice detail from
|
|
48
|
+
// Pulls full slice detail from a single context on a board and writes it into
|
|
49
49
|
// .slices/, mirroring the layout code-export.mjs's /api/generate handler produces
|
|
50
50
|
// (minus screen images, which only ever arrive via that push-based listener).
|
|
51
51
|
//
|
|
52
|
-
//
|
|
52
|
+
// kitDir here is null for modeling-kit (see cli.js's fetch action) — nothing
|
|
53
|
+
// nests .slices/ under .agent-modeling-kit/, so it lands at cwd instead.
|
|
54
|
+
// { cwd, kitDir, cfg: { token, organizationId, boardId, baseUrl }, opts: { context, sliceId?, sliceTitle? } }
|
|
53
55
|
export async function runFetch({ cwd, kitDir, cfg, opts = {} }) {
|
|
54
56
|
const baseUrl = cfg.baseUrl || DEFAULT_BASE_URL;
|
|
55
57
|
const headers = { 'x-token': cfg.token, 'x-board-id': cfg.boardId, 'x-user-id': 'cli-fetch' };
|
|
@@ -72,51 +74,42 @@ export async function runFetch({ cwd, kitDir, cfg, opts = {} }) {
|
|
|
72
74
|
return res.json();
|
|
73
75
|
}
|
|
74
76
|
|
|
75
|
-
console.log(`▶ Fetching
|
|
77
|
+
console.log(`▶ Fetching context "${opts.context}" from ${baseUrl} (board ${cfg.boardId})...`);
|
|
76
78
|
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
const
|
|
80
|
-
`${baseUrl}/api/org/${cfg.organizationId}/boards/${cfg.boardId}/nodes?type=MODEL_CONTEXT`,
|
|
81
|
-
'nodes?type=MODEL_CONTEXT',
|
|
82
|
-
);
|
|
83
|
-
if (!contextNodes?.length) {
|
|
84
|
-
console.log('ℹ️ No contexts found on this board.');
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
79
|
+
// Falls back to cwd when no kit is installed — fetch doesn't need kit-specific
|
|
80
|
+
// files, just somewhere to write .slices/.
|
|
81
|
+
const SLICES_DIR = join(kitDir || cwd, '.slices');
|
|
87
82
|
|
|
88
83
|
// /slicedata (buildSliceData) is per-context and returns full slice detail —
|
|
89
|
-
// commands/events/readmodels/screens/processors/specifications/comments
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
84
|
+
// commands/events/readmodels/screens/processors/specifications/comments. It
|
|
85
|
+
// resolves contextName by exact match, so no separate "list contexts" lookup
|
|
86
|
+
// is needed first.
|
|
87
|
+
const contextQuery = `contextName=${encodeURIComponent(opts.context)}`;
|
|
88
|
+
const payload = await fetchJson(
|
|
89
|
+
`${baseUrl}/api/org/${cfg.organizationId}/boards/${cfg.boardId}/slicedata?${contextQuery}`,
|
|
90
|
+
`slicedata?${contextQuery}`,
|
|
91
|
+
);
|
|
92
|
+
const { slices: allSlices } = payload;
|
|
93
|
+
|
|
94
|
+
if (allSlices.length) {
|
|
95
|
+
// Mirrors code-export.mjs's /api/generate: a raw per-context payload dump at
|
|
96
|
+
// .slices/<context>/config.json, alongside the per-slice output below — kept
|
|
97
|
+
// for parity with `listen` even though nothing in this repo reads it back.
|
|
98
|
+
const contextSlug = slugify(allSlices[0]?.context || opts.context || 'default') || 'default';
|
|
99
|
+
const baseFolder = join(SLICES_DIR, contextSlug);
|
|
100
|
+
mkdirSync(baseFolder, { recursive: true });
|
|
101
|
+
writeFileSync(join(baseFolder, 'config.json'), JSON.stringify(payload, null, 2));
|
|
103
102
|
}
|
|
104
103
|
|
|
105
104
|
if (!allSlices.length) {
|
|
106
|
-
console.log(
|
|
105
|
+
console.log(`ℹ️ No slices found in context "${opts.context}".`);
|
|
107
106
|
return;
|
|
108
107
|
}
|
|
109
108
|
|
|
110
|
-
// Falls back to cwd when no kit is installed — fetch doesn't need kit-specific
|
|
111
|
-
// files, just somewhere to write .slices/.
|
|
112
|
-
const SLICES_DIR = join(kitDir || cwd, '.slices');
|
|
113
|
-
const contextNames = new Set();
|
|
114
|
-
|
|
115
109
|
for (const slice of allSlices) {
|
|
116
110
|
// buildSliceData names this field `context`, not `contextName` (that's the
|
|
117
111
|
// /slicedata/slices summary endpoint's field) — read the one this endpoint sends.
|
|
118
|
-
const contextName = slice.context || 'default';
|
|
119
|
-
contextNames.add(contextName);
|
|
112
|
+
const contextName = slice.context || opts.context || 'default';
|
|
120
113
|
const contextSlug = slugify(contextName) || 'default';
|
|
121
114
|
const baseFolder = join(SLICES_DIR, contextSlug);
|
|
122
115
|
const sliceFolder = sliceFolderName(slice.title);
|
|
@@ -151,14 +144,9 @@ export async function runFetch({ cwd, kitDir, cfg, opts = {} }) {
|
|
|
151
144
|
writeFileSync(indexFile, JSON.stringify(sliceIndices, null, 2));
|
|
152
145
|
}
|
|
153
146
|
|
|
154
|
-
|
|
155
|
-
// belongs to one context — with several, any one choice would be arbitrary, so
|
|
156
|
-
// leave whatever `listen`/a prior fetch already wrote there untouched.
|
|
157
|
-
if (contextNames.size === 1) {
|
|
158
|
-
writeFileSync(join(SLICES_DIR, 'current_context.json'), JSON.stringify({ name: [...contextNames][0] }, null, 2));
|
|
159
|
-
}
|
|
147
|
+
writeFileSync(join(SLICES_DIR, 'current_context.json'), JSON.stringify({ name: opts.context }, null, 2));
|
|
160
148
|
|
|
161
|
-
console.log(`✅ Fetched ${allSlices.length} slice${allSlices.length === 1 ? '' : 's'}
|
|
149
|
+
console.log(`✅ Fetched ${allSlices.length} slice${allSlices.length === 1 ? '' : 's'} from context "${opts.context}" → ${relative(cwd, SLICES_DIR)}/`);
|
|
162
150
|
|
|
163
151
|
// --slice-id/--slice-title mirror load-slice's Step 4/5 — fetch+persist everything
|
|
164
152
|
// regardless, then just report the one the caller asked about.
|
package/package.json
CHANGED