@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.
- package/dist/bin/cascade.js +1 -1
- package/dist/tui/welcome.js +288 -105
- package/package.json +1 -1
package/dist/bin/cascade.js
CHANGED
|
@@ -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.
|
|
31
|
+
.version("0.3.0")
|
|
32
32
|
.addHelpText("beforeAll", BANNER);
|
|
33
33
|
// Global format option
|
|
34
34
|
function addFormat(cmd) {
|
package/dist/tui/welcome.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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.
|
|
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("
|
|
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
|
|
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(" ✓
|
|
136
|
+
console.log(green(" ✓ Saved to " + (0, config_js_1.getConfigPath)()));
|
|
125
137
|
}
|
|
126
138
|
else if (key) {
|
|
127
|
-
console.log(red(" ✗ Invalid
|
|
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
|
|
137
|
-
const ep = await
|
|
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(" ✓
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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")} ${
|
|
196
|
+
console.log(` ${dim("Step")} ${state.step} ${dim("Entities")} ${nodes.length} ${dim("Peak")} ${maxDev.toFixed(1)}%`);
|
|
182
197
|
console.log();
|
|
183
|
-
|
|
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(
|
|
201
|
+
console.log(` ${m.name.replace(/_/g, " ").padEnd(24)} ${color(bar)} ${m.dev.toFixed(1)}%`);
|
|
188
202
|
}
|
|
189
203
|
}
|
|
190
204
|
catch (err) {
|
|
191
|
-
|
|
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
|
|
208
|
+
console.log(dim(" Press any key..."));
|
|
196
209
|
await waitForKey();
|
|
197
210
|
}
|
|
198
|
-
async function
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
console.log(` ${
|
|
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("
|
|
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
|
|
269
|
+
console.log(dim(" Press any key..."));
|
|
221
270
|
await waitForKey();
|
|
222
271
|
}
|
|
223
|
-
function
|
|
224
|
-
|
|
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
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
|
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: "
|
|
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
|
|
246
|
-
items.push({ label:
|
|
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
|
|
447
|
+
items.push({ label: "Test Connection", hint: "Verify key and endpoint", action: testConnection });
|
|
250
448
|
}
|
|
251
|
-
items.push({ label: "
|
|
252
|
-
items
|
|
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
|
-
|
|
258
|
-
if (
|
|
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("
|
|
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(
|
|
471
|
+
console.log(` ${gold("▸")} ${bold(item.label.padEnd(22))} ${dim(item.hint)}`);
|
|
270
472
|
}
|
|
271
473
|
else {
|
|
272
|
-
console.log(` ${dim(item.label.padEnd(
|
|
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
|
-
//
|
|
312
|
-
|
|
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);
|