@eventmodelers/cli 0.0.2 → 0.0.4
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 +94 -45
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -132,6 +132,67 @@ async function prompt(question) {
|
|
|
132
132
|
});
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
// Reads a pasted block of credentials, which may span one line (minified JSON,
|
|
136
|
+
// or comma-separated values) or several (pretty-printed JSON). Stops as soon as
|
|
137
|
+
// the accumulated text parses, or on a blank line, so a single-line paste + Enter
|
|
138
|
+
// doesn't require a second Enter to finish.
|
|
139
|
+
async function promptPasteBlock() {
|
|
140
|
+
const lines = [];
|
|
141
|
+
while (lines.length < 20) {
|
|
142
|
+
const line = await new Promise((resolve) => getSharedRl().question('', resolve));
|
|
143
|
+
if (line.trim() === '') {
|
|
144
|
+
if (lines.length > 0) break;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
lines.push(line);
|
|
148
|
+
try {
|
|
149
|
+
JSON.parse(lines.join('\n'));
|
|
150
|
+
break;
|
|
151
|
+
} catch {
|
|
152
|
+
// not yet valid JSON — if it's a single CSV-looking line, that's complete too
|
|
153
|
+
if (lines.length === 1 && line.includes(',')) break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return lines.join('\n').trim();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Accepts either a JSON object (as copied from the account page) or a comma-separated
|
|
160
|
+
// line of values, in the same order as requiredFields (organizationId[, boardId], token),
|
|
161
|
+
// with an optional base URL anywhere in the list.
|
|
162
|
+
function parseCredentialsPaste(text, requiredFields) {
|
|
163
|
+
const trimmed = text.trim();
|
|
164
|
+
if (!trimmed) return null;
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const obj = JSON.parse(trimmed);
|
|
168
|
+
if (obj && typeof obj === 'object') {
|
|
169
|
+
const result = {};
|
|
170
|
+
if (obj.organizationId || obj.orgId) result.organizationId = obj.organizationId || obj.orgId;
|
|
171
|
+
if (obj.boardId) result.boardId = obj.boardId;
|
|
172
|
+
if (obj.token) result.token = obj.token;
|
|
173
|
+
if (obj.baseUrl) result.baseUrl = obj.baseUrl;
|
|
174
|
+
if (requiredFields.every((f) => result[f])) return result;
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
} catch {
|
|
178
|
+
// not JSON — fall through to comma-separated parsing
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const values = trimmed.split(/[,\n]/).map((v) => v.trim()).filter(Boolean);
|
|
182
|
+
const result = {};
|
|
183
|
+
const remaining = [];
|
|
184
|
+
for (const v of values) {
|
|
185
|
+
if (/^https?:\/\//i.test(v)) result.baseUrl = v;
|
|
186
|
+
else remaining.push(v);
|
|
187
|
+
}
|
|
188
|
+
if (remaining.length < requiredFields.length) return null;
|
|
189
|
+
requiredFields.forEach((field, i) => {
|
|
190
|
+
result[field] = remaining[i];
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
return requiredFields.every((f) => result[f]) ? result : null;
|
|
194
|
+
}
|
|
195
|
+
|
|
135
196
|
// Arrow-key single-select menu. Falls back to a numbered prompt on non-TTY stdin (e.g. piped input, CI).
|
|
136
197
|
async function selectPrompt(question, choices, defaultIndex = 0) {
|
|
137
198
|
if (!process.stdin.isTTY) {
|
|
@@ -386,21 +447,25 @@ async function installStack(stackKey, stackCfg, options = {}) {
|
|
|
386
447
|
console.log('\n ℹ️ --print — skipping credential prompt, missing fields must be set via EVENTMODELERS_* env vars or config.json');
|
|
387
448
|
} else if (stillMissing) {
|
|
388
449
|
const choice = await selectPrompt('How do you want to configure credentials?', [
|
|
389
|
-
{ label: 'Paste
|
|
390
|
-
{ label: 'Enter values
|
|
391
|
-
{ label: '
|
|
450
|
+
{ label: 'Paste values copied from app.eventmodelers.ai/account', value: 'paste' },
|
|
451
|
+
{ label: 'Enter values one by one', value: 'manual' },
|
|
452
|
+
{ label: 'Get instructions for configuring later', value: 'instructions' },
|
|
453
|
+
{ label: 'Skip for now', value: 'skip' },
|
|
392
454
|
], 1);
|
|
393
455
|
|
|
394
456
|
if (choice === 'paste') {
|
|
395
|
-
console.log(
|
|
396
|
-
console.log(
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
457
|
+
console.log('\n Copy your credentials from https://app.eventmodelers.ai/account,');
|
|
458
|
+
console.log(' then paste them below and press Enter:\n');
|
|
459
|
+
const pasted = await promptPasteBlock();
|
|
460
|
+
const parsed = parseCredentialsPaste(pasted, requiredFields);
|
|
461
|
+
if (parsed) {
|
|
462
|
+
config = { ...config, ...parsed };
|
|
463
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
464
|
+
console.log(`\n ✓ Saved to ${relative(targetDir, configPath)}`);
|
|
465
|
+
} else {
|
|
466
|
+
console.log(`\n ⚠️ Couldn't make sense of that paste — nothing was saved.`);
|
|
467
|
+
console.log(` Paste it into ${relative(targetDir, configPath)} yourself, or use /connect later.`);
|
|
468
|
+
}
|
|
404
469
|
} else if (choice === 'manual') {
|
|
405
470
|
console.log('\n🔑 Enter your Eventmodelers credentials:\n');
|
|
406
471
|
config.organizationId = await prompt(' Organization ID: ');
|
|
@@ -410,6 +475,16 @@ async function installStack(stackKey, stackCfg, options = {}) {
|
|
|
410
475
|
config.token = await prompt(' Token: ');
|
|
411
476
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
412
477
|
console.log(`\n ✓ Credentials saved to ${relative(targetDir, configPath)}`);
|
|
478
|
+
} else if (choice === 'instructions') {
|
|
479
|
+
console.log(`\n Paste your credentials into one of these locations:\n`);
|
|
480
|
+
console.log(` (a) ${configPath}`);
|
|
481
|
+
console.log(` (b) .eventmodelers/config.json in this directory or any parent directory\n`);
|
|
482
|
+
console.log(` The file should look like:`);
|
|
483
|
+
const sample = stackCfg.needsBoardId
|
|
484
|
+
? ` {\n "token": "...",\n "boardId": "...",\n "organizationId": "...",\n "baseUrl": "https://api.eventmodelers.ai"\n }\n`
|
|
485
|
+
: ` {\n "token": "...",\n "organizationId": "...",\n "baseUrl": "https://api.eventmodelers.ai"\n }\n`;
|
|
486
|
+
console.log(sample);
|
|
487
|
+
console.log(' Then re-run this installer, or just run the agent afterwards.\n');
|
|
413
488
|
} else {
|
|
414
489
|
console.log('\n ℹ️ Skipped — use /connect in Claude Code to add credentials later');
|
|
415
490
|
}
|
|
@@ -417,41 +492,15 @@ async function installStack(stackKey, stackCfg, options = {}) {
|
|
|
417
492
|
console.log('\n ✓ Config already present — skipping credential prompt');
|
|
418
493
|
}
|
|
419
494
|
|
|
420
|
-
//
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
console.log(' ✓ Using EVENTMODELERS_ANTHROPIC_BASE_URL / EVENTMODELERS_MODEL from environment — skipping prompt');
|
|
426
|
-
} else if (options.print) {
|
|
427
|
-
console.log(' ✓ --print — skipping prompt, keeping existing Claude execution settings');
|
|
428
|
-
} else {
|
|
429
|
-
const presetUrls = ['', 'http://localhost:8000', 'http://localhost:11434'];
|
|
430
|
-
let defaultUrlIndex = presetUrls.indexOf(config.anthropicBaseUrl || '');
|
|
431
|
-
if (defaultUrlIndex === -1) defaultUrlIndex = 0;
|
|
432
|
-
|
|
433
|
-
let anthropicBaseUrl = await selectPrompt('Anthropic Base URL:', [
|
|
434
|
-
{ label: 'None — use the default Claude Code endpoint', value: '' },
|
|
435
|
-
{ label: 'Local vLLM (http://localhost:8000)', value: 'http://localhost:8000' },
|
|
436
|
-
{ label: 'Local Ollama (http://localhost:11434)', value: 'http://localhost:11434' },
|
|
437
|
-
{ label: 'Custom…', value: '__custom__' },
|
|
438
|
-
], defaultUrlIndex);
|
|
439
|
-
|
|
440
|
-
if (anthropicBaseUrl === '__custom__') {
|
|
441
|
-
anthropicBaseUrl = await prompt(' Custom Anthropic Base URL: ');
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
const claudeModel = await prompt(` Model ${config.model ? `[${config.model}]` : '(optional, press Enter to skip)'}: `);
|
|
445
|
-
|
|
446
|
-
if (anthropicBaseUrl) config.anthropicBaseUrl = anthropicBaseUrl;
|
|
447
|
-
else delete config.anthropicBaseUrl;
|
|
448
|
-
if (claudeModel) config.model = claudeModel;
|
|
449
|
-
}
|
|
450
|
-
|
|
495
|
+
// Claude vs. Ollama, and any custom Anthropic-compatible base URL, is a `run`-time
|
|
496
|
+
// choice (`eventmodelers run` vs `--ollama`) — not an install-time one. Power users
|
|
497
|
+
// can still pin config.anthropicBaseUrl/model via EVENTMODELERS_ANTHROPIC_BASE_URL /
|
|
498
|
+
// EVENTMODELERS_MODEL env vars or by editing config.json directly; loadEffectiveConfig
|
|
499
|
+
// already picks those up above, so nothing further to do here.
|
|
451
500
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
452
501
|
console.log(`\n ✓ Saved to ${relative(targetDir, configPath)}`);
|
|
453
502
|
|
|
454
|
-
// ---
|
|
503
|
+
// --- 6. MCP server in .claude/settings.json ---
|
|
455
504
|
console.log('\n🔌 Configuring MCP server...');
|
|
456
505
|
console.log(' Registers the Eventmodelers MCP server in .claude/settings.json so Claude Code can call modeling tools directly.\n');
|
|
457
506
|
const claudeSettingsDir = join(targetDir, '.claude');
|
|
@@ -510,7 +559,7 @@ async function installStack(stackKey, stackCfg, options = {}) {
|
|
|
510
559
|
}
|
|
511
560
|
}
|
|
512
561
|
|
|
513
|
-
// ---
|
|
562
|
+
// --- 7. Install manifest (drives precise `uninstall` later) ---
|
|
514
563
|
// Only the footprint listed here is ever removed by `uninstall` — the root
|
|
515
564
|
// scaffold (step 2) is real project source the user builds on, so it's
|
|
516
565
|
// deliberately left out and never touched by uninstall.
|
package/package.json
CHANGED