@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.
Files changed (2) hide show
  1. package/cli.js +94 -45
  2. 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 JSON copied from app.eventmodelers.ai/account', value: 'paste' },
390
- { label: 'Enter values manually now', value: 'manual' },
391
- { label: 'Skip configure later with /connect', value: 'skip' },
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(`\n Paste your credentials JSON into one of these locations:\n`);
396
- console.log(` (a) ${configPath}`);
397
- console.log(` (b) .eventmodelers/config.json in this directory or any parent directory\n`);
398
- console.log(` The file should look like:`);
399
- const sample = stackCfg.needsBoardId
400
- ? ` {\n "token": "...",\n "boardId": "...",\n "organizationId": "...",\n "baseUrl": "https://api.eventmodelers.ai"\n }\n`
401
- : ` {\n "token": "...",\n "organizationId": "...",\n "baseUrl": "https://api.eventmodelers.ai"\n }\n`;
402
- console.log(sample);
403
- console.log(' Then re-run this installer, or just run the agent afterwards.\n');
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
- // --- 6. Claude execution (optional) ---
421
- console.log('\n🧠 Configuring Claude execution (optional)...');
422
- console.log(' Point the agent at a local vLLM/Ollama endpoint and/or pin a specific model, instead of the default Claude Code setup.');
423
-
424
- if (process.env.EVENTMODELERS_ANTHROPIC_BASE_URL || process.env.EVENTMODELERS_MODEL) {
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
- // --- 7. MCP server in .claude/settings.json ---
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
- // --- 8. Install manifest (drives precise `uninstall` later) ---
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventmodelers/cli",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
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": {