@eventmodelers/cli 0.0.3 → 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 +87 -12
  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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventmodelers/cli",
3
- "version": "0.0.3",
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": {