@metisos/cascade-cli 0.1.0 → 0.2.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.
@@ -12,11 +12,24 @@ const portfolio_js_1 = require("../commands/portfolio.js");
12
12
  const graph_js_1 = require("../commands/graph.js");
13
13
  const skills_js_1 = require("../commands/skills.js");
14
14
  const config_js_2 = require("../commands/config.js");
15
+ const welcome_js_1 = require("../tui/welcome.js");
16
+ const BANNER = `
17
+ \x1b[33m ██████╗ █████╗ ███████╗ ██████╗ █████╗ ██████╗ ███████╗\x1b[0m
18
+ \x1b[33m ██╔════╝██╔══██╗██╔════╝██╔════╝██╔══██╗██╔══██╗██╔════╝\x1b[0m
19
+ \x1b[33m ██║ ███████║███████╗██║ ███████║██║ ██║█████╗ \x1b[0m
20
+ \x1b[33m ██║ ██╔══██║╚════██║██║ ██╔══██║██║ ██║██╔══╝ \x1b[0m
21
+ \x1b[33m ╚██████╗██║ ██║███████║╚██████╗██║ ██║██████╔╝███████╗\x1b[0m
22
+ \x1b[33m ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝╚═════╝ ╚══════╝\x1b[0m
23
+
24
+ \x1b[2m Real-Time Event Propagation Intelligence\x1b[0m
25
+ \x1b[2m See the cascade before it hits.\x1b[0m
26
+ `;
15
27
  const program = new commander_1.Command();
16
28
  program
17
29
  .name("cascade")
18
30
  .description("Cascade CLI — Real-time geopolitical and economic intelligence")
19
- .version("0.1.0");
31
+ .version("0.2.0")
32
+ .addHelpText("beforeAll", BANNER);
20
33
  // Global format option
21
34
  function addFormat(cmd) {
22
35
  return cmd.option("-f, --format <format>", "Output format: table, json, csv", (0, config_js_1.loadConfig)().output || "table");
@@ -81,5 +94,14 @@ function handleError(err) {
81
94
  console.error(`Error: ${err.message}`);
82
95
  process.exit(1);
83
96
  }
84
- // Parse
85
- program.parse();
97
+ // No args → launch interactive welcome menu
98
+ if (process.argv.length <= 2) {
99
+ (0, welcome_js_1.startWelcome)().catch((err) => {
100
+ console.error(err.message);
101
+ process.exit(1);
102
+ });
103
+ }
104
+ else {
105
+ // Subcommand provided → run command mode
106
+ program.parse();
107
+ }
@@ -0,0 +1 @@
1
+ export declare function startWelcome(): Promise<void>;
@@ -0,0 +1,336 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.startWelcome = startWelcome;
7
+ const readline_1 = __importDefault(require("readline"));
8
+ const child_process_1 = require("child_process");
9
+ const config_js_1 = require("../config.js");
10
+ const client_js_1 = require("../client.js");
11
+ const BANNER = `
12
+ \x1b[33m ██████╗ █████╗ ███████╗ ██████╗ █████╗ ██████╗ ███████╗\x1b[0m
13
+ \x1b[33m ██╔════╝██╔══██╗██╔════╝██╔════╝██╔══██╗██╔══██╗██╔════╝\x1b[0m
14
+ \x1b[33m ██║ ███████║███████╗██║ ███████║██║ ██║█████╗ \x1b[0m
15
+ \x1b[33m ██║ ██╔══██║╚════██║██║ ██╔══██║██║ ██║██╔══╝ \x1b[0m
16
+ \x1b[33m ╚██████╗██║ ██║███████║╚██████╗██║ ██║██████╔╝███████╗\x1b[0m
17
+ \x1b[33m ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝╚═════╝ ╚══════╝\x1b[0m
18
+ \x1b[2m Real-Time Event Propagation Intelligence\x1b[0m
19
+ `;
20
+ function dim(s) { return `\x1b[2m${s}\x1b[0m`; }
21
+ function bold(s) { return `\x1b[1m${s}\x1b[0m`; }
22
+ function gold(s) { return `\x1b[33m${s}\x1b[0m`; }
23
+ function green(s) { return `\x1b[32m${s}\x1b[0m`; }
24
+ function red(s) { return `\x1b[31m${s}\x1b[0m`; }
25
+ function cyan(s) { return `\x1b[36m${s}\x1b[0m`; }
26
+ function clearScreen() {
27
+ process.stdout.write("\x1b[2J\x1b[H");
28
+ }
29
+ function hideCursor() { process.stdout.write("\x1b[?25l"); }
30
+ function showCursor() { process.stdout.write("\x1b[?25h"); }
31
+ function prompt(question) {
32
+ const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
33
+ return new Promise((resolve) => {
34
+ rl.question(question, (answer) => {
35
+ rl.close();
36
+ resolve(answer.trim());
37
+ });
38
+ });
39
+ }
40
+ async function signInWithBrowser() {
41
+ showCursor();
42
+ const config = (0, config_js_1.loadConfig)();
43
+ const endpoint = config.endpoint;
44
+ console.log();
45
+ console.log(bold(" Sign in with browser"));
46
+ console.log(dim(" Opening your browser to authorize the CLI..."));
47
+ console.log();
48
+ // Step 1: Create a pending auth session
49
+ let code;
50
+ try {
51
+ const res = await fetch(`${endpoint}/api/cli-auth`, { method: "POST" });
52
+ const data = await res.json();
53
+ code = data.code;
54
+ }
55
+ catch {
56
+ console.log(red(" ✗ Failed to reach Cascade. Check your endpoint."));
57
+ hideCursor();
58
+ await pause(2000);
59
+ return;
60
+ }
61
+ // Step 2: Open browser
62
+ const authUrl = `${endpoint}/cli-auth?code=${code}`;
63
+ console.log(dim(` URL: ${authUrl}`));
64
+ console.log();
65
+ try {
66
+ const platform = process.platform;
67
+ if (platform === "darwin")
68
+ (0, child_process_1.execSync)(`open "${authUrl}"`);
69
+ else if (platform === "win32")
70
+ (0, child_process_1.execSync)(`start "${authUrl}"`);
71
+ else
72
+ (0, child_process_1.execSync)(`xdg-open "${authUrl}" 2>/dev/null || echo ""`);
73
+ console.log(dim(" Browser opened. Approve the request, then come back here."));
74
+ }
75
+ catch {
76
+ console.log(` Open this URL in your browser:`);
77
+ console.log(gold(` ${authUrl}`));
78
+ }
79
+ console.log();
80
+ console.log(dim(" Waiting for approval..."));
81
+ // Step 3: Poll for approval
82
+ const startTime = Date.now();
83
+ const timeout = 10 * 60 * 1000; // 10 minutes
84
+ while (Date.now() - startTime < timeout) {
85
+ await pause(2000);
86
+ try {
87
+ const res = await fetch(`${endpoint}/api/cli-auth?code=${code}`);
88
+ const data = await res.json();
89
+ if (data.status === "approved" && data.apiKey) {
90
+ (0, config_js_1.saveConfig)({ api_key: data.apiKey });
91
+ console.log();
92
+ console.log(green(" ✓ Authenticated! API key saved."));
93
+ hideCursor();
94
+ await pause(1500);
95
+ return;
96
+ }
97
+ if (data.status === "expired") {
98
+ console.log(red(" ✗ Session expired. Try again."));
99
+ hideCursor();
100
+ await pause(2000);
101
+ return;
102
+ }
103
+ // Still pending — show a dot
104
+ process.stdout.write(dim("."));
105
+ }
106
+ catch {
107
+ // Network error — keep trying
108
+ }
109
+ }
110
+ console.log();
111
+ console.log(red(" ✗ Timed out. Try again."));
112
+ hideCursor();
113
+ await pause(2000);
114
+ }
115
+ async function setupApiKey() {
116
+ showCursor();
117
+ console.log();
118
+ console.log(bold(" Configure API Key"));
119
+ console.log(dim(" Get your key from Portal → Settings → Developer & API"));
120
+ console.log();
121
+ const key = await prompt(" Paste your API key: ");
122
+ if (key && key.startsWith("csk_")) {
123
+ (0, config_js_1.saveConfig)({ api_key: key });
124
+ console.log(green(" ✓ API key saved to " + (0, config_js_1.getConfigPath)()));
125
+ }
126
+ else if (key) {
127
+ console.log(red(" ✗ Invalid key format. Keys start with csk_live_"));
128
+ }
129
+ hideCursor();
130
+ await pause(1500);
131
+ }
132
+ async function setupEndpoint() {
133
+ showCursor();
134
+ console.log();
135
+ const config = (0, config_js_1.loadConfig)();
136
+ console.log(dim(` Current endpoint: ${config.endpoint}`));
137
+ const ep = await prompt(" Enter endpoint URL: ");
138
+ if (ep) {
139
+ (0, config_js_1.saveConfig)({ endpoint: ep });
140
+ console.log(green(" ✓ Endpoint set to " + ep));
141
+ }
142
+ hideCursor();
143
+ await pause(1000);
144
+ }
145
+ async function testConnection() {
146
+ console.log();
147
+ console.log(dim(" Testing connection..."));
148
+ try {
149
+ const client = new client_js_1.CascadeClient();
150
+ const { data } = await client.getState();
151
+ const state = data;
152
+ const nodes = (state.graph?.nodes ?? []);
153
+ console.log(green(` ✓ Connected — Step ${state.step}, ${nodes.length} entities`));
154
+ }
155
+ catch (err) {
156
+ const msg = err instanceof Error ? err.message : "Unknown error";
157
+ console.log(red(` ✗ Connection failed: ${msg}`));
158
+ }
159
+ await pause(2000);
160
+ }
161
+ async function quickStatus() {
162
+ try {
163
+ const client = new client_js_1.CascadeClient();
164
+ const { data } = await client.getState();
165
+ const state = data;
166
+ const nodes = state.graph?.nodes ?? [];
167
+ const nodeArr = nodes;
168
+ let maxDev = 0;
169
+ const movers = [];
170
+ for (const n of nodeArr) {
171
+ const devs = n.deviation_pct ?? [];
172
+ const avg = devs.reduce((s, v) => s + Math.abs(v), 0) / (devs.length || 1);
173
+ if (avg > maxDev)
174
+ maxDev = avg;
175
+ movers.push({ name: String(n.name), dev: avg });
176
+ }
177
+ movers.sort((a, b) => b.dev - a.dev);
178
+ const health = maxDev > 15 ? red("CRITICAL") : maxDev > 8 ? gold("WARNING") : green("NOMINAL");
179
+ console.log();
180
+ console.log(` ${bold("System Status")} ${health}`);
181
+ console.log(` ${dim("Step")} ${state.step} ${dim("Entities")} ${nodeArr.length} ${dim("Peak deviation")} ${maxDev.toFixed(1)}%`);
182
+ console.log();
183
+ console.log(` ${dim("Top movers:")}`);
184
+ for (const m of movers.slice(0, 5)) {
185
+ const bar = "█".repeat(Math.min(Math.round(m.dev / 2), 20));
186
+ 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)}%`);
188
+ }
189
+ }
190
+ catch (err) {
191
+ const msg = err instanceof Error ? err.message : "Unknown error";
192
+ console.log(red(` Error: ${msg}`));
193
+ }
194
+ console.log();
195
+ console.log(dim(" Press any key to continue..."));
196
+ await waitForKey();
197
+ }
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)}`);
216
+ }
217
+ console.log();
218
+ console.log(dim(" All commands support --format json|csv|table"));
219
+ console.log();
220
+ console.log(dim(" Press any key to continue..."));
221
+ await waitForKey();
222
+ }
223
+ function pause(ms) {
224
+ return new Promise((resolve) => setTimeout(resolve, ms));
225
+ }
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
+ });
236
+ });
237
+ }
238
+ async function startWelcome() {
239
+ const config = (0, config_js_1.loadConfig)();
240
+ const hasKey = !!config.api_key;
241
+ const items = [];
242
+ if (hasKey) {
243
+ items.push({ label: "Dashboard", hint: "View system status and top movers", action: quickStatus });
244
+ }
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 });
247
+ items.push({ label: "Set Endpoint", hint: config.endpoint, action: setupEndpoint });
248
+ if (hasKey) {
249
+ items.push({ label: "Test Connection", hint: "Verify API key and endpoint", action: testConnection });
250
+ }
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); } });
253
+ let selected = 0;
254
+ const render = () => {
255
+ clearScreen();
256
+ console.log(BANNER);
257
+ // Config status
258
+ if (hasKey) {
259
+ console.log(` ${green("●")} Connected ${dim("Key:")} ${config.api_key.slice(0, 16)}... ${dim("Endpoint:")} ${config.endpoint}`);
260
+ }
261
+ else {
262
+ console.log(` ${red("●")} Not configured ${dim("Set your API key to get started")}`);
263
+ }
264
+ console.log();
265
+ // Menu
266
+ for (let i = 0; i < items.length; i++) {
267
+ const item = items[i];
268
+ if (i === selected) {
269
+ console.log(` ${gold("▸")} ${bold(item.label.padEnd(20))} ${dim(item.hint)}`);
270
+ }
271
+ else {
272
+ console.log(` ${dim(item.label.padEnd(20))} ${dim(item.hint)}`);
273
+ }
274
+ }
275
+ console.log();
276
+ console.log(dim(" ↑↓ Navigate Enter Select q Quit"));
277
+ };
278
+ hideCursor();
279
+ render();
280
+ // Input loop
281
+ process.stdin.setRawMode(true);
282
+ process.stdin.resume();
283
+ const handleKey = async (data) => {
284
+ const key = data.toString();
285
+ if (key === "q" || key === "\x03") {
286
+ // q or Ctrl+C
287
+ showCursor();
288
+ clearScreen();
289
+ process.exit(0);
290
+ }
291
+ if (key === "\x1b[A") {
292
+ // Up arrow
293
+ selected = (selected - 1 + items.length) % items.length;
294
+ render();
295
+ return;
296
+ }
297
+ if (key === "\x1b[B") {
298
+ // Down arrow
299
+ selected = (selected + 1) % items.length;
300
+ render();
301
+ return;
302
+ }
303
+ if (key === "\r" || key === "\n") {
304
+ // Enter
305
+ process.stdin.removeListener("data", handleKey);
306
+ process.stdin.setRawMode(false);
307
+ process.stdin.pause();
308
+ clearScreen();
309
+ console.log(BANNER);
310
+ 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); } });
327
+ if (selected >= items.length)
328
+ selected = 0;
329
+ process.stdin.setRawMode(true);
330
+ process.stdin.resume();
331
+ process.stdin.on("data", handleKey);
332
+ render();
333
+ }
334
+ };
335
+ process.stdin.on("data", handleKey);
336
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metisos/cascade-cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.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"