@eventmodelers/cli 0.0.24 → 0.0.26

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 (3) hide show
  1. package/cli.js +30 -12
  2. package/lib/fetch.js +18 -1
  3. package/package.json +1 -1
package/cli.js CHANGED
@@ -18,7 +18,7 @@ import { execSync, spawn } from 'child_process';
18
18
  import { createInterface, emitKeypressEvents, moveCursor, clearScreenDown } from 'readline';
19
19
  import { homedir } from 'os';
20
20
  import { randomUUID } from 'crypto';
21
- import { runFetch } from './lib/fetch.js';
21
+ import { runFetch, FetchAuthError } from './lib/fetch.js';
22
22
 
23
23
  const __filename = fileURLToPath(import.meta.url);
24
24
  const __dirname = dirname(__filename);
@@ -1110,10 +1110,11 @@ program
1110
1110
 
1111
1111
  // Commands exempt from the "is a kit installed here?" gate below: init (with or
1112
1112
  // without --modeling) is what installs one in the first place, init-config only
1113
- // ever touches credentials, and stacks/status/config/uninstall are read-only or
1113
+ // ever touches credentials, stacks/status/config/uninstall are read-only or
1114
1114
  // cleanup commands that are meant to work — and report something useful — whether
1115
- // or not a kit is present.
1116
- const NO_INIT_REQUIRED = new Set(['init', 'init-config', 'stacks', 'status', 'config', 'uninstall']);
1115
+ // or not a kit is present, and fetch only needs credentials plus somewhere to write
1116
+ // .slices/ (cwd, absent a kit dir — see lib/fetch.js), no kit-specific files.
1117
+ const NO_INIT_REQUIRED = new Set(['init', 'init-config', 'stacks', 'status', 'config', 'uninstall', 'fetch']);
1117
1118
 
1118
1119
  program.hook('preAction', (_thisCommand, actionCommand) => {
1119
1120
  if (NO_INIT_REQUIRED.has(actionCommand.name())) return;
@@ -1467,14 +1468,16 @@ program
1467
1468
  const effective = loadEffectiveConfig(cwd, kitDir, explicitConfig);
1468
1469
  let cfg = effective.config;
1469
1470
 
1471
+ // Same default (project-root .eventmodelers/config.json, or --config) that
1472
+ // installStack uses — kept identical rather than deriving a path from
1473
+ // `effective`, which can point at a kit-dir-scoped config instead.
1474
+ const configPath = explicitConfig ? resolve(cwd, explicitConfig) : join(cwd, '.eventmodelers', 'config.json');
1470
1475
  const requiredFields = ['organizationId', 'boardId', 'token'];
1471
- if (requiredFields.some((f) => !cfg[f])) {
1472
- // Same default (project-root .eventmodelers/config.json, or --config) that
1473
- // installStack uses kept identical rather than deriving a path from
1474
- // `effective`, which can point at a kit-dir-scoped config instead.
1475
- const configPath = explicitConfig ? resolve(cwd, explicitConfig) : join(cwd, '.eventmodelers', 'config.json');
1476
- // Same prompt (paste/manual/instructions/skip) `install`/`init-config` use —
1477
- // reusing it here means `fetch` also works as a first-run credential setup.
1476
+
1477
+ // Same prompt (paste/manual/instructions/skip) `install`/`init-config` use
1478
+ // reusing it here means `fetch` also works as a first-run credential setup.
1479
+ // Also re-entered below if the API rejects whatever we already had.
1480
+ async function promptForCredentials() {
1478
1481
  cfg = await configureCredentials({
1479
1482
  config: cfg,
1480
1483
  configPath,
@@ -1489,7 +1492,22 @@ program
1489
1492
  process.exit(1);
1490
1493
  }
1491
1494
  }
1492
- await runFetch({ cwd, kitDir, cfg, opts });
1495
+
1496
+ if (requiredFields.some((f) => !cfg[f])) await promptForCredentials();
1497
+
1498
+ try {
1499
+ await runFetch({ cwd, kitDir, cfg, opts });
1500
+ } catch (err) {
1501
+ if (!(err instanceof FetchAuthError)) throw err;
1502
+ // Present but wrong, not missing — the connect skill's Step 4 (Verify) treats
1503
+ // 401/403/404 the same way: clear the field that's implicated and re-prompt,
1504
+ // rather than leaving the caller stuck re-running with the same bad value.
1505
+ console.error(`❌ ${err.message}`);
1506
+ if (err.status === 404) delete cfg.boardId;
1507
+ else delete cfg.token;
1508
+ await promptForCredentials();
1509
+ await runFetch({ cwd, kitDir, cfg, opts });
1510
+ }
1493
1511
  });
1494
1512
 
1495
1513
  program
package/lib/fetch.js CHANGED
@@ -3,6 +3,18 @@ import { join, relative } from 'path';
3
3
 
4
4
  const DEFAULT_BASE_URL = 'https://api.eventmodelers.ai';
5
5
 
6
+ // Thrown instead of exiting on 401/403/404 — these mean the *credentials* (not the
7
+ // network/server) are the problem, so the caller gets a chance to re-prompt and retry
8
+ // instead of just dying, the same way the connect skill's Step 4 (Verify) reacts to
9
+ // each status. Other statuses (500, etc.) still exit directly — reconfiguring
10
+ // credentials wouldn't fix those.
11
+ export class FetchAuthError extends Error {
12
+ constructor(status, message) {
13
+ super(message);
14
+ this.status = status;
15
+ }
16
+ }
17
+
6
18
  function readJsonSafe(path) {
7
19
  if (!path || !existsSync(path)) return {};
8
20
  try {
@@ -50,6 +62,9 @@ export async function runFetch({ cwd, kitDir, cfg, opts = {} }) {
50
62
  console.error(`❌ Request failed (${what}): ${err.message}`);
51
63
  process.exit(1);
52
64
  }
65
+ if (res.status === 401) throw new FetchAuthError(401, `${what}: invalid or expired token`);
66
+ if (res.status === 403) throw new FetchAuthError(403, `${what}: token's organization does not match this board`);
67
+ if (res.status === 404) throw new FetchAuthError(404, `${what}: board not found`);
53
68
  if (!res.ok) {
54
69
  console.error(`❌ ${what}: HTTP ${res.status}`);
55
70
  process.exit(1);
@@ -92,7 +107,9 @@ export async function runFetch({ cwd, kitDir, cfg, opts = {} }) {
92
107
  return;
93
108
  }
94
109
 
95
- const SLICES_DIR = join(kitDir, '.slices');
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');
96
113
  const contextNames = new Set();
97
114
 
98
115
  for (const slice of allSlices) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventmodelers/cli",
3
- "version": "0.0.24",
3
+ "version": "0.0.26",
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": {