@metisos/cascade-cli 0.2.0 → 0.3.0

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.
@@ -28,7 +28,7 @@ const program = new commander_1.Command();
28
28
  program
29
29
  .name("cascade")
30
30
  .description("Cascade CLI — Real-time geopolitical and economic intelligence")
31
- .version("0.2.0")
31
+ .version("0.3.0")
32
32
  .addHelpText("beforeAll", BANNER);
33
33
  // Global format option
34
34
  function addFormat(cmd) {
@@ -23,20 +23,39 @@ function gold(s) { return `\x1b[33m${s}\x1b[0m`; }
23
23
  function green(s) { return `\x1b[32m${s}\x1b[0m`; }
24
24
  function red(s) { return `\x1b[31m${s}\x1b[0m`; }
25
25
  function cyan(s) { return `\x1b[36m${s}\x1b[0m`; }
26
- function clearScreen() {
27
- process.stdout.write("\x1b[2J\x1b[H");
28
- }
26
+ function clearScreen() { process.stdout.write("\x1b[2J\x1b[H"); }
29
27
  function hideCursor() { process.stdout.write("\x1b[?25l"); }
30
28
  function showCursor() { process.stdout.write("\x1b[?25h"); }
31
- function prompt(question) {
29
+ function promptLine(question) {
32
30
  const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
33
31
  return new Promise((resolve) => {
34
- rl.question(question, (answer) => {
35
- rl.close();
36
- resolve(answer.trim());
32
+ rl.question(question, (answer) => { rl.close(); resolve(answer.trim()); });
33
+ });
34
+ }
35
+ function pause(ms) {
36
+ return new Promise((resolve) => setTimeout(resolve, ms));
37
+ }
38
+ function waitForKey() {
39
+ return new Promise((resolve) => {
40
+ const wasRaw = process.stdin.isRaw;
41
+ process.stdin.setRawMode(true);
42
+ process.stdin.resume();
43
+ process.stdin.once("data", () => {
44
+ process.stdin.setRawMode(wasRaw ?? false);
45
+ process.stdin.pause();
46
+ resolve();
37
47
  });
38
48
  });
39
49
  }
50
+ function getClient() {
51
+ try {
52
+ return new client_js_1.CascadeClient();
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ // ── Actions ──
40
59
  async function signInWithBrowser() {
41
60
  showCursor();
42
61
  const config = (0, config_js_1.loadConfig)();
@@ -45,7 +64,6 @@ async function signInWithBrowser() {
45
64
  console.log(bold(" Sign in with browser"));
46
65
  console.log(dim(" Opening your browser to authorize the CLI..."));
47
66
  console.log();
48
- // Step 1: Create a pending auth session
49
67
  let code;
50
68
  try {
51
69
  const res = await fetch(`${endpoint}/api/cli-auth`, { method: "POST" });
@@ -58,7 +76,6 @@ async function signInWithBrowser() {
58
76
  await pause(2000);
59
77
  return;
60
78
  }
61
- // Step 2: Open browser
62
79
  const authUrl = `${endpoint}/cli-auth?code=${code}`;
63
80
  console.log(dim(` URL: ${authUrl}`));
64
81
  console.log();
@@ -78,10 +95,8 @@ async function signInWithBrowser() {
78
95
  }
79
96
  console.log();
80
97
  console.log(dim(" Waiting for approval..."));
81
- // Step 3: Poll for approval
82
98
  const startTime = Date.now();
83
- const timeout = 10 * 60 * 1000; // 10 minutes
84
- while (Date.now() - startTime < timeout) {
99
+ while (Date.now() - startTime < 10 * 60 * 1000) {
85
100
  await pause(2000);
86
101
  try {
87
102
  const res = await fetch(`${endpoint}/api/cli-auth?code=${code}`);
@@ -100,31 +115,28 @@ async function signInWithBrowser() {
100
115
  await pause(2000);
101
116
  return;
102
117
  }
103
- // Still pending — show a dot
104
118
  process.stdout.write(dim("."));
105
119
  }
106
- catch {
107
- // Network error — keep trying
108
- }
120
+ catch { /* keep trying */ }
109
121
  }
110
122
  console.log();
111
- console.log(red(" ✗ Timed out. Try again."));
123
+ console.log(red(" ✗ Timed out."));
112
124
  hideCursor();
113
125
  await pause(2000);
114
126
  }
115
127
  async function setupApiKey() {
116
128
  showCursor();
117
129
  console.log();
118
- console.log(bold(" Configure API Key"));
130
+ console.log(bold(" Paste API Key"));
119
131
  console.log(dim(" Get your key from Portal → Settings → Developer & API"));
120
132
  console.log();
121
- const key = await prompt(" Paste your API key: ");
133
+ const key = await promptLine(" API key: ");
122
134
  if (key && key.startsWith("csk_")) {
123
135
  (0, config_js_1.saveConfig)({ api_key: key });
124
- console.log(green(" ✓ API key saved to " + (0, config_js_1.getConfigPath)()));
136
+ console.log(green(" ✓ Saved to " + (0, config_js_1.getConfigPath)()));
125
137
  }
126
138
  else if (key) {
127
- console.log(red(" ✗ Invalid key format. Keys start with csk_live_"));
139
+ console.log(red(" ✗ Invalid format. Keys start with csk_"));
128
140
  }
129
141
  hideCursor();
130
142
  await pause(1500);
@@ -133,18 +145,18 @@ async function setupEndpoint() {
133
145
  showCursor();
134
146
  console.log();
135
147
  const config = (0, config_js_1.loadConfig)();
136
- console.log(dim(` Current endpoint: ${config.endpoint}`));
137
- const ep = await prompt(" Enter endpoint URL: ");
148
+ console.log(dim(` Current: ${config.endpoint}`));
149
+ const ep = await promptLine(" Endpoint URL: ");
138
150
  if (ep) {
139
151
  (0, config_js_1.saveConfig)({ endpoint: ep });
140
- console.log(green(" ✓ Endpoint set to " + ep));
152
+ console.log(green(" ✓ Set to " + ep));
141
153
  }
142
154
  hideCursor();
143
155
  await pause(1000);
144
156
  }
145
157
  async function testConnection() {
146
158
  console.log();
147
- console.log(dim(" Testing connection..."));
159
+ console.log(dim(" Testing..."));
148
160
  try {
149
161
  const client = new client_js_1.CascadeClient();
150
162
  const { data } = await client.getState();
@@ -153,21 +165,24 @@ async function testConnection() {
153
165
  console.log(green(` ✓ Connected — Step ${state.step}, ${nodes.length} entities`));
154
166
  }
155
167
  catch (err) {
156
- const msg = err instanceof Error ? err.message : "Unknown error";
157
- console.log(red(` ✗ Connection failed: ${msg}`));
168
+ console.log(red(` ✗ ${err instanceof Error ? err.message : "Failed"}`));
158
169
  }
159
170
  await pause(2000);
160
171
  }
161
- async function quickStatus() {
172
+ async function viewDashboard() {
173
+ const client = getClient();
174
+ if (!client) {
175
+ console.log(red("\n Not configured. Set API key first."));
176
+ await pause(1500);
177
+ return;
178
+ }
162
179
  try {
163
- const client = new client_js_1.CascadeClient();
164
180
  const { data } = await client.getState();
165
181
  const state = data;
166
- const nodes = state.graph?.nodes ?? [];
167
- const nodeArr = nodes;
182
+ const nodes = (state.graph?.nodes ?? []);
168
183
  let maxDev = 0;
169
184
  const movers = [];
170
- for (const n of nodeArr) {
185
+ for (const n of nodes) {
171
186
  const devs = n.deviation_pct ?? [];
172
187
  const avg = devs.reduce((s, v) => s + Math.abs(v), 0) / (devs.length || 1);
173
188
  if (avg > maxDev)
@@ -178,98 +193,285 @@ async function quickStatus() {
178
193
  const health = maxDev > 15 ? red("CRITICAL") : maxDev > 8 ? gold("WARNING") : green("NOMINAL");
179
194
  console.log();
180
195
  console.log(` ${bold("System Status")} ${health}`);
181
- console.log(` ${dim("Step")} ${state.step} ${dim("Entities")} ${nodeArr.length} ${dim("Peak deviation")} ${maxDev.toFixed(1)}%`);
196
+ console.log(` ${dim("Step")} ${state.step} ${dim("Entities")} ${nodes.length} ${dim("Peak")} ${maxDev.toFixed(1)}%`);
182
197
  console.log();
183
- console.log(` ${dim("Top movers:")}`);
184
- for (const m of movers.slice(0, 5)) {
198
+ for (const m of movers.slice(0, 8)) {
185
199
  const bar = "█".repeat(Math.min(Math.round(m.dev / 2), 20));
186
200
  const color = m.dev > 10 ? red : m.dev > 5 ? gold : green;
187
- console.log(` ${m.name.replace(/_/g, " ").padEnd(22)} ${color(bar)} ${m.dev.toFixed(1)}%`);
201
+ console.log(` ${m.name.replace(/_/g, " ").padEnd(24)} ${color(bar)} ${m.dev.toFixed(1)}%`);
188
202
  }
189
203
  }
190
204
  catch (err) {
191
- const msg = err instanceof Error ? err.message : "Unknown error";
192
- console.log(red(` Error: ${msg}`));
205
+ console.log(red(`\n ${err instanceof Error ? err.message : "Failed"}`));
193
206
  }
194
207
  console.log();
195
- console.log(dim(" Press any key to continue..."));
208
+ console.log(dim(" Press any key..."));
196
209
  await waitForKey();
197
210
  }
198
- async function showHelp() {
199
- console.log();
200
- console.log(bold(" Command Reference"));
201
- console.log();
202
- const cmds = [
203
- ["cascade status", "System health overview"],
204
- ["cascade entities", "All entities with deviations"],
205
- ["cascade entity <name>", "Detail for specific entity"],
206
- ["cascade events", "Event feed (--watch for live)"],
207
- ["cascade prices", "All tracked tickers"],
208
- ["cascade price <entity>", "Live price data"],
209
- ["cascade chat \"question\"", "Ask Cascade AI"],
210
- ["cascade dossier --entities X,Y", "Intelligence report"],
211
- ["cascade skills", "AI agent skills file"],
212
- ["cascade config show", "Show configuration"],
213
- ];
214
- for (const [cmd, desc] of cmds) {
215
- console.log(` ${cyan(cmd.padEnd(32))} ${dim(desc)}`);
211
+ async function viewEntities() {
212
+ const client = getClient();
213
+ if (!client) {
214
+ console.log(red("\n Not configured."));
215
+ await pause(1500);
216
+ return;
217
+ }
218
+ try {
219
+ const { data } = await client.getState();
220
+ const nodes = data.graph?.nodes ?? [];
221
+ const rows = nodes.map((n) => {
222
+ const devs = n.deviation_pct ?? [];
223
+ const avg = devs.reduce((s, v) => s + Math.abs(v), 0) / (devs.length || 1);
224
+ return { name: String(n.name), cat: String(n.category), dev: avg, econ: devs[1] ?? 0 };
225
+ }).sort((a, b) => b.dev - a.dev);
226
+ console.log();
227
+ console.log(` ${dim("Entity".padEnd(26))} ${dim("Category".padEnd(10))} ${dim("Deviation".padEnd(10))} ${dim("Econ")}`);
228
+ console.log(` ${"─".repeat(60)}`);
229
+ for (const r of rows.slice(0, 20)) {
230
+ const color = r.dev > 10 ? red : r.dev > 5 ? gold : green;
231
+ console.log(` ${r.name.replace(/_/g, " ").padEnd(26)} ${r.cat.padEnd(10)} ${color(r.dev.toFixed(1).padStart(5) + "%")} ${r.econ >= 0 ? "+" : ""}${r.econ.toFixed(1)}%`);
232
+ }
233
+ if (rows.length > 20)
234
+ console.log(dim(`\n ...and ${rows.length - 20} more`));
235
+ }
236
+ catch (err) {
237
+ console.log(red(`\n ${err instanceof Error ? err.message : "Failed"}`));
216
238
  }
217
239
  console.log();
218
- console.log(dim(" All commands support --format json|csv|table"));
240
+ console.log(dim(" Press any key..."));
241
+ await waitForKey();
242
+ }
243
+ async function viewEvents() {
244
+ const client = getClient();
245
+ if (!client) {
246
+ console.log(red("\n Not configured."));
247
+ await pause(1500);
248
+ return;
249
+ }
250
+ try {
251
+ const { data } = await client.getEvents(15);
252
+ const events = data.events ?? [];
253
+ console.log();
254
+ console.log(` ${dim("Time".padEnd(10))} ${dim("Type".padEnd(22))} ${dim("Entity".padEnd(20))} ${dim("Sev")}`);
255
+ console.log(` ${"─".repeat(60)}`);
256
+ for (const e of events) {
257
+ const time = String(e.timestamp ?? "").slice(11, 19);
258
+ const type = String(e.event_type ?? "").replace(/_/g, " ");
259
+ const entity = String(e.entity_name ?? "");
260
+ const sev = Math.round((e.severity ?? 0) * 100);
261
+ const color = sev >= 70 ? red : sev >= 30 ? gold : green;
262
+ console.log(` ${time.padEnd(10)} ${type.padEnd(22)} ${entity.replace(/_/g, " ").padEnd(20)} ${color(sev + "%")}`);
263
+ }
264
+ }
265
+ catch (err) {
266
+ console.log(red(`\n ${err instanceof Error ? err.message : "Failed"}`));
267
+ }
219
268
  console.log();
220
- console.log(dim(" Press any key to continue..."));
269
+ console.log(dim(" Press any key..."));
221
270
  await waitForKey();
222
271
  }
223
- function pause(ms) {
224
- return new Promise((resolve) => setTimeout(resolve, ms));
272
+ async function viewPrices() {
273
+ const client = getClient();
274
+ if (!client) {
275
+ console.log(red("\n Not configured."));
276
+ await pause(1500);
277
+ return;
278
+ }
279
+ try {
280
+ const { data } = await client.getPrices();
281
+ const tickers = data.tickers ?? [];
282
+ console.log();
283
+ for (const t of tickers) {
284
+ const name = String(t.name).replace(/_/g, " ");
285
+ const symbol = String(t.symbol);
286
+ try {
287
+ const { data: priceData } = await client.getPrice(String(t.name), "5d");
288
+ const bars = priceData.data ?? [];
289
+ if (bars.length >= 2) {
290
+ const last = bars[bars.length - 1];
291
+ const first = bars[0];
292
+ const price = last.close.toFixed(2);
293
+ const change = (last.close - first.close);
294
+ const pct = (change / first.close) * 100;
295
+ const color = change >= 0 ? green : red;
296
+ console.log(` ${name.padEnd(18)} ${dim(symbol.padEnd(10))} ${price.padStart(10)} ${color((change >= 0 ? "+" : "") + pct.toFixed(2) + "%")}`);
297
+ }
298
+ else {
299
+ console.log(` ${name.padEnd(18)} ${dim(symbol.padEnd(10))} ${dim("no data")}`);
300
+ }
301
+ }
302
+ catch {
303
+ console.log(` ${name.padEnd(18)} ${dim(symbol.padEnd(10))} ${dim("—")}`);
304
+ }
305
+ }
306
+ }
307
+ catch (err) {
308
+ console.log(red(`\n ${err instanceof Error ? err.message : "Failed"}`));
309
+ }
310
+ console.log();
311
+ console.log(dim(" Press any key..."));
312
+ await waitForKey();
225
313
  }
226
- function waitForKey() {
227
- return new Promise((resolve) => {
228
- const wasRaw = process.stdin.isRaw;
229
- process.stdin.setRawMode(true);
230
- process.stdin.resume();
231
- process.stdin.once("data", () => {
232
- process.stdin.setRawMode(wasRaw ?? false);
233
- process.stdin.pause();
234
- resolve();
235
- });
314
+ async function interactiveChat() {
315
+ const client = getClient();
316
+ if (!client) {
317
+ console.log(red("\n Not configured."));
318
+ await pause(1500);
319
+ return;
320
+ }
321
+ showCursor();
322
+ clearScreen();
323
+ console.log(BANNER);
324
+ console.log(bold(" Cascade Intelligence Chat"));
325
+ console.log(dim(" Ask about entities, events, risks, prices. Type 'exit' to return.\n"));
326
+ const rl = readline_1.default.createInterface({
327
+ input: process.stdin,
328
+ output: process.stdout,
329
+ prompt: gold(" You: "),
330
+ });
331
+ rl.prompt();
332
+ rl.on("line", async (line) => {
333
+ const msg = line.trim();
334
+ if (!msg) {
335
+ rl.prompt();
336
+ return;
337
+ }
338
+ if (msg.toLowerCase() === "exit" || msg.toLowerCase() === "quit") {
339
+ rl.close();
340
+ hideCursor();
341
+ return;
342
+ }
343
+ console.log(dim(" Thinking..."));
344
+ try {
345
+ const { data } = await client.chat(msg);
346
+ console.log();
347
+ // Word wrap the response
348
+ const content = data.content;
349
+ const lines = content.split("\n");
350
+ for (const l of lines) {
351
+ const words = l.split(" ");
352
+ let line = " ";
353
+ for (const w of words) {
354
+ if (line.length + w.length > 90) {
355
+ console.log(cyan(line));
356
+ line = " " + w + " ";
357
+ }
358
+ else {
359
+ line += w + " ";
360
+ }
361
+ }
362
+ if (line.trim())
363
+ console.log(cyan(line));
364
+ }
365
+ console.log();
366
+ }
367
+ catch (err) {
368
+ console.log(red(` Error: ${err instanceof Error ? err.message : "Failed"}`));
369
+ console.log();
370
+ }
371
+ rl.prompt();
372
+ });
373
+ rl.on("close", () => {
374
+ // Return to menu handled by caller
236
375
  });
376
+ // Wait for chat to end
377
+ await new Promise((resolve) => { rl.on("close", resolve); });
237
378
  }
238
- async function startWelcome() {
379
+ async function viewCorrelations() {
380
+ const client = getClient();
381
+ if (!client) {
382
+ console.log(red("\n Not configured."));
383
+ await pause(1500);
384
+ return;
385
+ }
386
+ try {
387
+ const { data } = await client.getCorrelations(1);
388
+ const d = data;
389
+ const entities = d.entities ?? [];
390
+ const nObs = d.n_observations ?? 0;
391
+ console.log();
392
+ console.log(` ${bold("Correlation Matrix")} ${dim(`(Econ, ${nObs} observations)`)}`);
393
+ if (nObs < 30) {
394
+ console.log(dim(`\n Need ${30 - nObs} more observations for correlation data.`));
395
+ }
396
+ else {
397
+ const matrix = d.empirical ?? [];
398
+ // Show top correlated pairs
399
+ const pairs = [];
400
+ for (let i = 0; i < entities.length; i++) {
401
+ for (let j = i + 1; j < entities.length; j++) {
402
+ pairs.push({ a: entities[i], b: entities[j], corr: matrix[i]?.[j] ?? 0 });
403
+ }
404
+ }
405
+ pairs.sort((a, b) => Math.abs(b.corr) - Math.abs(a.corr));
406
+ console.log(`\n ${dim("Strongest relationships:")}`);
407
+ for (const p of pairs.slice(0, 12)) {
408
+ const color = p.corr > 0.3 ? green : p.corr < -0.3 ? red : dim;
409
+ const sign = p.corr >= 0 ? "+" : "";
410
+ console.log(` ${p.a.replace(/_/g, " ").padEnd(20)} ↔ ${p.b.replace(/_/g, " ").padEnd(20)} ${color(sign + p.corr.toFixed(2))}`);
411
+ }
412
+ }
413
+ }
414
+ catch (err) {
415
+ console.log(red(`\n ${err instanceof Error ? err.message : "Failed"}`));
416
+ }
417
+ console.log();
418
+ console.log(dim(" Press any key..."));
419
+ await waitForKey();
420
+ }
421
+ async function generateSkills() {
422
+ showCursor();
423
+ console.log();
424
+ const { skillsCommand } = await import("../commands/skills.js");
425
+ await skillsCommand({ output: "CASCADE_SKILLS.md" });
426
+ hideCursor();
427
+ await pause(1500);
428
+ }
429
+ // ── Main Menu ──
430
+ function buildMenu() {
239
431
  const config = (0, config_js_1.loadConfig)();
240
432
  const hasKey = !!config.api_key;
241
433
  const items = [];
242
434
  if (hasKey) {
243
- items.push({ label: "Dashboard", hint: "View system status and top movers", action: quickStatus });
435
+ items.push({ label: "Dashboard", hint: "System status and top movers", action: viewDashboard });
436
+ items.push({ label: "Chat", hint: "Ask Cascade AI anything", action: interactiveChat });
437
+ items.push({ label: "Entities", hint: "All entities with deviations", action: viewEntities });
438
+ items.push({ label: "Events", hint: "Recent event feed", action: viewEvents });
439
+ items.push({ label: "Prices", hint: "Live market data", action: viewPrices });
440
+ items.push({ label: "Correlations", hint: "Entity co-movement", action: viewCorrelations });
441
+ items.push({ label: "Generate Skills", hint: "CASCADE_SKILLS.md for AI agents", action: generateSkills, separator: true });
244
442
  }
245
- items.push({ label: "Sign in with browser", hint: "Authenticate via your Cascade portal", action: signInWithBrowser });
246
- items.push({ label: hasKey ? "Change API Key" : "Paste API Key", hint: hasKey ? `Current: ${config.api_key.slice(0, 16)}...` : "Manually enter a key", action: setupApiKey });
443
+ items.push({ label: "Sign in with browser", hint: "Authenticate via Cascade portal", action: signInWithBrowser });
444
+ items.push({ label: "Paste API Key", hint: hasKey ? `Current: ${config.api_key.slice(0, 16)}...` : "Manually enter a key", action: setupApiKey });
247
445
  items.push({ label: "Set Endpoint", hint: config.endpoint, action: setupEndpoint });
248
446
  if (hasKey) {
249
- items.push({ label: "Test Connection", hint: "Verify API key and endpoint", action: testConnection });
447
+ items.push({ label: "Test Connection", hint: "Verify key and endpoint", action: testConnection });
250
448
  }
251
- items.push({ label: "Commands", hint: "View all available commands", action: showHelp });
252
- items.push({ label: "Quit", hint: "Exit Cascade CLI", action: () => { showCursor(); process.exit(0); } });
449
+ items.push({ label: "Quit", hint: "", action: () => { showCursor(); clearScreen(); process.exit(0); } });
450
+ return items;
451
+ }
452
+ async function startWelcome() {
453
+ let items = buildMenu();
253
454
  let selected = 0;
254
455
  const render = () => {
255
456
  clearScreen();
256
457
  console.log(BANNER);
257
- // Config status
258
- if (hasKey) {
458
+ const config = (0, config_js_1.loadConfig)();
459
+ if (config.api_key) {
259
460
  console.log(` ${green("●")} Connected ${dim("Key:")} ${config.api_key.slice(0, 16)}... ${dim("Endpoint:")} ${config.endpoint}`);
260
461
  }
261
462
  else {
262
- console.log(` ${red("●")} Not configured ${dim("Set your API key to get started")}`);
463
+ console.log(` ${red("●")} Not configured ${dim("Sign in or paste API key to get started")}`);
263
464
  }
264
465
  console.log();
265
- // Menu
266
466
  for (let i = 0; i < items.length; i++) {
267
467
  const item = items[i];
468
+ if (item.separator && i > 0)
469
+ console.log();
268
470
  if (i === selected) {
269
- console.log(` ${gold("▸")} ${bold(item.label.padEnd(20))} ${dim(item.hint)}`);
471
+ console.log(` ${gold("▸")} ${bold(item.label.padEnd(22))} ${dim(item.hint)}`);
270
472
  }
271
473
  else {
272
- console.log(` ${dim(item.label.padEnd(20))} ${dim(item.hint)}`);
474
+ console.log(` ${dim(item.label.padEnd(22))} ${dim(item.hint)}`);
273
475
  }
274
476
  }
275
477
  console.log();
@@ -277,53 +479,34 @@ async function startWelcome() {
277
479
  };
278
480
  hideCursor();
279
481
  render();
280
- // Input loop
281
482
  process.stdin.setRawMode(true);
282
483
  process.stdin.resume();
283
484
  const handleKey = async (data) => {
284
485
  const key = data.toString();
285
486
  if (key === "q" || key === "\x03") {
286
- // q or Ctrl+C
287
487
  showCursor();
288
488
  clearScreen();
289
489
  process.exit(0);
290
490
  }
291
491
  if (key === "\x1b[A") {
292
- // Up arrow
293
492
  selected = (selected - 1 + items.length) % items.length;
294
493
  render();
295
494
  return;
296
495
  }
297
496
  if (key === "\x1b[B") {
298
- // Down arrow
299
497
  selected = (selected + 1) % items.length;
300
498
  render();
301
499
  return;
302
500
  }
303
501
  if (key === "\r" || key === "\n") {
304
- // Enter
305
502
  process.stdin.removeListener("data", handleKey);
306
503
  process.stdin.setRawMode(false);
307
504
  process.stdin.pause();
308
505
  clearScreen();
309
506
  console.log(BANNER);
310
507
  await items[selected].action();
311
- // After action, re-render menu
312
- // Rebuild items in case config changed
313
- const newConfig = (0, config_js_1.loadConfig)();
314
- const newHasKey = !!newConfig.api_key;
315
- items.length = 0;
316
- if (newHasKey) {
317
- items.push({ label: "Dashboard", hint: "View system status and top movers", action: quickStatus });
318
- }
319
- items.push({ label: "Sign in with browser", hint: "Authenticate via your Cascade portal", action: signInWithBrowser });
320
- items.push({ label: newHasKey ? "Change API Key" : "Paste API Key", hint: newHasKey ? `Current: ${newConfig.api_key.slice(0, 16)}...` : "Manually enter a key", action: setupApiKey });
321
- items.push({ label: "Set Endpoint", hint: newConfig.endpoint, action: setupEndpoint });
322
- if (newHasKey) {
323
- items.push({ label: "Test Connection", hint: "Verify API key and endpoint", action: testConnection });
324
- }
325
- items.push({ label: "Commands", hint: "View all available commands", action: showHelp });
326
- items.push({ label: "Quit", hint: "Exit Cascade CLI", action: () => { showCursor(); process.exit(0); } });
508
+ // Rebuild menu (config may have changed)
509
+ items = buildMenu();
327
510
  if (selected >= items.length)
328
511
  selected = 0;
329
512
  process.stdin.setRawMode(true);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metisos/cascade-cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Cascade CLI — Real-time geopolitical and economic intelligence from your terminal",
5
5
  "bin": {
6
6
  "cascade": "./dist/bin/cascade.js"