@metisos/cascade-cli 0.1.1 → 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.
- package/dist/bin/cascade.js +11 -6
- package/dist/tui/welcome.d.ts +1 -0
- package/dist/tui/welcome.js +336 -0
- package/package.json +1 -1
package/dist/bin/cascade.js
CHANGED
|
@@ -12,6 +12,7 @@ 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");
|
|
15
16
|
const BANNER = `
|
|
16
17
|
\x1b[33m ██████╗ █████╗ ███████╗ ██████╗ █████╗ ██████╗ ███████╗\x1b[0m
|
|
17
18
|
\x1b[33m ██╔════╝██╔══██╗██╔════╝██╔════╝██╔══██╗██╔══██╗██╔════╝\x1b[0m
|
|
@@ -27,7 +28,7 @@ const program = new commander_1.Command();
|
|
|
27
28
|
program
|
|
28
29
|
.name("cascade")
|
|
29
30
|
.description("Cascade CLI — Real-time geopolitical and economic intelligence")
|
|
30
|
-
.version("0.
|
|
31
|
+
.version("0.2.0")
|
|
31
32
|
.addHelpText("beforeAll", BANNER);
|
|
32
33
|
// Global format option
|
|
33
34
|
function addFormat(cmd) {
|
|
@@ -93,10 +94,14 @@ function handleError(err) {
|
|
|
93
94
|
console.error(`Error: ${err.message}`);
|
|
94
95
|
process.exit(1);
|
|
95
96
|
}
|
|
96
|
-
// No args →
|
|
97
|
+
// No args → launch interactive welcome menu
|
|
97
98
|
if (process.argv.length <= 2) {
|
|
98
|
-
|
|
99
|
-
|
|
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();
|
|
100
107
|
}
|
|
101
|
-
// Parse
|
|
102
|
-
program.parse();
|
|
@@ -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
|
+
}
|