@metisos/cascade-cli 0.1.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.d.ts +2 -0
- package/dist/bin/cascade.js +85 -0
- package/dist/client.d.ts +37 -0
- package/dist/client.js +85 -0
- package/dist/commands/chat.d.ts +9 -0
- package/dist/commands/chat.js +34 -0
- package/dist/commands/config.d.ts +1 -0
- package/dist/commands/config.js +40 -0
- package/dist/commands/entities.d.ts +12 -0
- package/dist/commands/entities.js +42 -0
- package/dist/commands/events.d.ts +7 -0
- package/dist/commands/events.js +46 -0
- package/dist/commands/graph.d.ts +12 -0
- package/dist/commands/graph.js +28 -0
- package/dist/commands/portfolio.d.ts +4 -0
- package/dist/commands/portfolio.js +10 -0
- package/dist/commands/prices.d.ts +8 -0
- package/dist/commands/prices.js +40 -0
- package/dist/commands/skills.d.ts +4 -0
- package/dist/commands/skills.js +169 -0
- package/dist/commands/status.d.ts +4 -0
- package/dist/commands/status.js +37 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +41 -0
- package/dist/formatters/index.d.ts +2 -0
- package/dist/formatters/index.js +65 -0
- package/package.json +37 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const config_js_1 = require("../config.js");
|
|
6
|
+
const status_js_1 = require("../commands/status.js");
|
|
7
|
+
const entities_js_1 = require("../commands/entities.js");
|
|
8
|
+
const events_js_1 = require("../commands/events.js");
|
|
9
|
+
const prices_js_1 = require("../commands/prices.js");
|
|
10
|
+
const chat_js_1 = require("../commands/chat.js");
|
|
11
|
+
const portfolio_js_1 = require("../commands/portfolio.js");
|
|
12
|
+
const graph_js_1 = require("../commands/graph.js");
|
|
13
|
+
const skills_js_1 = require("../commands/skills.js");
|
|
14
|
+
const config_js_2 = require("../commands/config.js");
|
|
15
|
+
const program = new commander_1.Command();
|
|
16
|
+
program
|
|
17
|
+
.name("cascade")
|
|
18
|
+
.description("Cascade CLI — Real-time geopolitical and economic intelligence")
|
|
19
|
+
.version("0.1.0");
|
|
20
|
+
// Global format option
|
|
21
|
+
function addFormat(cmd) {
|
|
22
|
+
return cmd.option("-f, --format <format>", "Output format: table, json, csv", (0, config_js_1.loadConfig)().output || "table");
|
|
23
|
+
}
|
|
24
|
+
// Status
|
|
25
|
+
addFormat(program.command("status").description("System health, step, entity count")).action((opts) => (0, status_js_1.statusCommand)(opts).catch(handleError));
|
|
26
|
+
// Entities
|
|
27
|
+
addFormat(program.command("entities").description("List all entities with deviations")).action((opts) => (0, entities_js_1.entitiesCommand)(opts).catch(handleError));
|
|
28
|
+
addFormat(program
|
|
29
|
+
.command("entity <name>")
|
|
30
|
+
.description("Entity detail + connections")
|
|
31
|
+
.option("--history", "Show deviation trajectory")
|
|
32
|
+
.option("--steps <n>", "History steps", "30")).action((name, opts) => (0, entities_js_1.entityCommand)(name, { ...opts, steps: parseInt(opts.steps) }).catch(handleError));
|
|
33
|
+
addFormat(program.command("search <query>").description("Search entities")).action((query, opts) => (0, entities_js_1.searchCommand)(query, opts).catch(handleError));
|
|
34
|
+
// Events
|
|
35
|
+
addFormat(program
|
|
36
|
+
.command("events")
|
|
37
|
+
.description("Recent event feed")
|
|
38
|
+
.option("--limit <n>", "Number of events", "20")
|
|
39
|
+
.option("--type <type>", "Filter by event type")
|
|
40
|
+
.option("--watch", "Live streaming mode")).action((opts) => (0, events_js_1.eventsCommand)({ ...opts, limit: parseInt(opts.limit) }).catch(handleError));
|
|
41
|
+
// Prices
|
|
42
|
+
addFormat(program.command("prices").description("All tracked tickers")).action((opts) => (0, prices_js_1.pricesCommand)(opts).catch(handleError));
|
|
43
|
+
addFormat(program
|
|
44
|
+
.command("price <entity>")
|
|
45
|
+
.description("Live price for an entity")
|
|
46
|
+
.option("--period <period>", "Price period: 1d, 5d, 1mo, 3mo", "5d")).action((entity, opts) => (0, prices_js_1.priceCommand)(entity, opts).catch(handleError));
|
|
47
|
+
// Chat
|
|
48
|
+
addFormat(program.command("chat <message>").description("Ask Cascade AI")).action((message, opts) => (0, chat_js_1.chatCommand)(message, opts).catch(handleError));
|
|
49
|
+
// Dossier
|
|
50
|
+
addFormat(program
|
|
51
|
+
.command("dossier")
|
|
52
|
+
.description("Generate intelligence report")
|
|
53
|
+
.requiredOption("--entities <list>", "Comma-separated entity names")
|
|
54
|
+
.option("--type <type>", "Report type", "strategic_assessment")).action((opts) => (0, chat_js_1.dossierCommand)(opts).catch(handleError));
|
|
55
|
+
// Portfolio
|
|
56
|
+
addFormat(program.command("portfolio").description("Portfolio entities with risk")).action((opts) => (0, portfolio_js_1.portfolioCommand)(opts).catch(handleError));
|
|
57
|
+
// Graph
|
|
58
|
+
addFormat(program.command("graph").description("Network node + edge counts")).action((opts) => (0, graph_js_1.graphCommand)(opts).catch(handleError));
|
|
59
|
+
addFormat(program
|
|
60
|
+
.command("correlations")
|
|
61
|
+
.description("Pairwise correlation matrix")
|
|
62
|
+
.option("--dim <n>", "Dimension: 0=Stability, 1=Econ, 2=Political, 3=Social", "1")).action((opts) => (0, graph_js_1.correlationsCommand)({ ...opts, dim: parseInt(opts.dim) }).catch(handleError));
|
|
63
|
+
addFormat(program
|
|
64
|
+
.command("lags")
|
|
65
|
+
.description("Lead/lag relationship matrix")
|
|
66
|
+
.option("--dim <n>", "Dimension", "1")).action((opts) => (0, graph_js_1.lagsCommand)({ ...opts, dim: parseInt(opts.dim) }).catch(handleError));
|
|
67
|
+
// Skills
|
|
68
|
+
program
|
|
69
|
+
.command("skills")
|
|
70
|
+
.description("Generate CASCADE_SKILLS.md for AI agents")
|
|
71
|
+
.option("--output <path>", "Write to file instead of stdout")
|
|
72
|
+
.option("--static", "Static output without live context")
|
|
73
|
+
.action((opts) => (0, skills_js_1.skillsCommand)(opts).catch(handleError));
|
|
74
|
+
// Config
|
|
75
|
+
program
|
|
76
|
+
.command("config <action> [key] [value]")
|
|
77
|
+
.description("Manage configuration (show, set, path)")
|
|
78
|
+
.action((action, key, value) => (0, config_js_2.configCommand)(action, key, value));
|
|
79
|
+
// Error handler
|
|
80
|
+
function handleError(err) {
|
|
81
|
+
console.error(`Error: ${err.message}`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
// Parse
|
|
85
|
+
program.parse();
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface ApiResponse<T = unknown> {
|
|
2
|
+
data: T;
|
|
3
|
+
meta: {
|
|
4
|
+
step?: number;
|
|
5
|
+
timestamp: string;
|
|
6
|
+
org_id?: string;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export interface ApiError {
|
|
10
|
+
error: {
|
|
11
|
+
code: string;
|
|
12
|
+
message: string;
|
|
13
|
+
retry_after?: number;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export declare class CascadeClient {
|
|
17
|
+
private endpoint;
|
|
18
|
+
private apiKey;
|
|
19
|
+
constructor(endpoint?: string, apiKey?: string);
|
|
20
|
+
private request;
|
|
21
|
+
getState(): Promise<ApiResponse<Record<string, unknown>>>;
|
|
22
|
+
getEvents(limit?: number): Promise<ApiResponse<Record<string, unknown>>>;
|
|
23
|
+
getGraph(): Promise<ApiResponse<Record<string, unknown>>>;
|
|
24
|
+
getEntity(name: string): Promise<ApiResponse<Record<string, unknown>>>;
|
|
25
|
+
getEntityHistory(name: string, steps?: number): Promise<ApiResponse<Record<string, unknown>>>;
|
|
26
|
+
searchEntities(query: string): Promise<ApiResponse<unknown[]>>;
|
|
27
|
+
getPrices(): Promise<ApiResponse<Record<string, unknown>>>;
|
|
28
|
+
getPrice(entity: string, period?: string): Promise<ApiResponse<Record<string, unknown>>>;
|
|
29
|
+
getPortfolio(): Promise<ApiResponse<unknown[]>>;
|
|
30
|
+
getCorrelations(dim?: number): Promise<ApiResponse<Record<string, unknown>>>;
|
|
31
|
+
getLags(dim?: number): Promise<ApiResponse<Record<string, unknown>>>;
|
|
32
|
+
chat(message: string): Promise<ApiResponse<{
|
|
33
|
+
content: string;
|
|
34
|
+
}>>;
|
|
35
|
+
dossier(entities: string[], reportType?: string): Promise<ApiResponse<Record<string, unknown>>>;
|
|
36
|
+
pollSources(): Promise<ApiResponse<Record<string, unknown>>>;
|
|
37
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CascadeClient = void 0;
|
|
4
|
+
const config_js_1 = require("./config.js");
|
|
5
|
+
class CascadeClient {
|
|
6
|
+
endpoint;
|
|
7
|
+
apiKey;
|
|
8
|
+
constructor(endpoint, apiKey) {
|
|
9
|
+
const config = (0, config_js_1.loadConfig)();
|
|
10
|
+
this.endpoint = endpoint || config.endpoint;
|
|
11
|
+
this.apiKey = apiKey || config.api_key;
|
|
12
|
+
if (!this.apiKey) {
|
|
13
|
+
throw new Error("No API key configured. Run: cascade config set api-key <your-key>");
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async request(path, opts = {}) {
|
|
17
|
+
const url = `${this.endpoint}/api/v1${path}`;
|
|
18
|
+
const headers = {
|
|
19
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
20
|
+
"Content-Type": "application/json",
|
|
21
|
+
...opts.headers,
|
|
22
|
+
};
|
|
23
|
+
const res = await fetch(url, { ...opts, headers });
|
|
24
|
+
const body = await res.json();
|
|
25
|
+
if (!res.ok) {
|
|
26
|
+
const err = body;
|
|
27
|
+
const msg = err.error?.message || `HTTP ${res.status}`;
|
|
28
|
+
const code = err.error?.code || "unknown";
|
|
29
|
+
throw new Error(`[${code}] ${msg}`);
|
|
30
|
+
}
|
|
31
|
+
return body;
|
|
32
|
+
}
|
|
33
|
+
async getState() {
|
|
34
|
+
return this.request("/state");
|
|
35
|
+
}
|
|
36
|
+
async getEvents(limit = 20) {
|
|
37
|
+
return this.request(`/events?limit=${limit}`);
|
|
38
|
+
}
|
|
39
|
+
async getGraph() {
|
|
40
|
+
return this.request("/graph");
|
|
41
|
+
}
|
|
42
|
+
async getEntity(name) {
|
|
43
|
+
return this.request(`/entities/${encodeURIComponent(name)}`);
|
|
44
|
+
}
|
|
45
|
+
async getEntityHistory(name, steps = 30) {
|
|
46
|
+
return this.request(`/entities/${encodeURIComponent(name)}/history?steps=${steps}`);
|
|
47
|
+
}
|
|
48
|
+
async searchEntities(query) {
|
|
49
|
+
return this.request(`/entities/search?q=${encodeURIComponent(query)}`);
|
|
50
|
+
}
|
|
51
|
+
async getPrices() {
|
|
52
|
+
return this.request("/prices");
|
|
53
|
+
}
|
|
54
|
+
async getPrice(entity, period = "5d") {
|
|
55
|
+
return this.request(`/prices/${encodeURIComponent(entity)}?period=${period}`);
|
|
56
|
+
}
|
|
57
|
+
async getPortfolio() {
|
|
58
|
+
return this.request("/portfolio");
|
|
59
|
+
}
|
|
60
|
+
async getCorrelations(dim = 1) {
|
|
61
|
+
return this.request(`/calibration/correlations?dim=${dim}`);
|
|
62
|
+
}
|
|
63
|
+
async getLags(dim = 1) {
|
|
64
|
+
return this.request(`/calibration/lags?dim=${dim}`);
|
|
65
|
+
}
|
|
66
|
+
async chat(message) {
|
|
67
|
+
return this.request("/chat", {
|
|
68
|
+
method: "POST",
|
|
69
|
+
body: JSON.stringify({ message }),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async dossier(entities, reportType = "strategic_assessment") {
|
|
73
|
+
return this.request("/dossier", {
|
|
74
|
+
method: "POST",
|
|
75
|
+
body: JSON.stringify({ entities, report_type: reportType }),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
async pollSources() {
|
|
79
|
+
return this.request("/sources/poll", {
|
|
80
|
+
method: "POST",
|
|
81
|
+
body: "{}",
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
exports.CascadeClient = CascadeClient;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type OutputFormat } from "../formatters/index.js";
|
|
2
|
+
export declare function chatCommand(message: string, opts: {
|
|
3
|
+
format: OutputFormat;
|
|
4
|
+
}): Promise<void>;
|
|
5
|
+
export declare function dossierCommand(opts: {
|
|
6
|
+
format: OutputFormat;
|
|
7
|
+
entities: string;
|
|
8
|
+
type?: string;
|
|
9
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.chatCommand = chatCommand;
|
|
4
|
+
exports.dossierCommand = dossierCommand;
|
|
5
|
+
const client_js_1 = require("../client.js");
|
|
6
|
+
const index_js_1 = require("../formatters/index.js");
|
|
7
|
+
async function chatCommand(message, opts) {
|
|
8
|
+
const client = new client_js_1.CascadeClient();
|
|
9
|
+
const { data } = await client.chat(message);
|
|
10
|
+
if (opts.format === "json") {
|
|
11
|
+
console.log((0, index_js_1.formatOutput)(data, "json"));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
console.log();
|
|
15
|
+
console.log(data.content);
|
|
16
|
+
console.log();
|
|
17
|
+
}
|
|
18
|
+
async function dossierCommand(opts) {
|
|
19
|
+
const entityList = opts.entities.split(",").map((e) => e.trim());
|
|
20
|
+
const client = new client_js_1.CascadeClient();
|
|
21
|
+
const { data } = await client.dossier(entityList, opts.type);
|
|
22
|
+
if (opts.format === "json") {
|
|
23
|
+
console.log((0, index_js_1.formatOutput)(data, "json"));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const report = data;
|
|
27
|
+
console.log(`\n ${report.title}`);
|
|
28
|
+
console.log(` ${report.generated_at} | Step ${report.step} | ${report.system_health}\n`);
|
|
29
|
+
const sections = report.sections ?? [];
|
|
30
|
+
for (const s of sections) {
|
|
31
|
+
console.log(` ${s.heading.toUpperCase()}`);
|
|
32
|
+
console.log(` ${s.content}\n`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function configCommand(action: string, key?: string, value?: string): void;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.configCommand = configCommand;
|
|
4
|
+
const config_js_1 = require("../config.js");
|
|
5
|
+
function configCommand(action, key, value) {
|
|
6
|
+
if (action === "show" || action === "list") {
|
|
7
|
+
const config = (0, config_js_1.loadConfig)();
|
|
8
|
+
console.log(` Config: ${(0, config_js_1.getConfigPath)()}\n`);
|
|
9
|
+
console.log(` api-key: ${config.api_key ? config.api_key.slice(0, 16) + "..." : "(not set)"}`);
|
|
10
|
+
console.log(` endpoint: ${config.endpoint}`);
|
|
11
|
+
console.log(` output: ${config.output}`);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (action === "set" && key && value) {
|
|
15
|
+
const keyMap = {
|
|
16
|
+
"api-key": "api_key",
|
|
17
|
+
"apikey": "api_key",
|
|
18
|
+
"key": "api_key",
|
|
19
|
+
"endpoint": "endpoint",
|
|
20
|
+
"url": "endpoint",
|
|
21
|
+
"output": "output",
|
|
22
|
+
"format": "output",
|
|
23
|
+
};
|
|
24
|
+
const configKey = keyMap[key.toLowerCase()];
|
|
25
|
+
if (!configKey) {
|
|
26
|
+
console.error(`Unknown config key: ${key}`);
|
|
27
|
+
console.error(`Valid keys: api-key, endpoint, output`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
(0, config_js_1.saveConfig)({ [configKey]: value });
|
|
31
|
+
console.log(` Set ${key} = ${configKey === "api_key" ? value.slice(0, 16) + "..." : value}`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (action === "path") {
|
|
35
|
+
console.log((0, config_js_1.getConfigPath)());
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
console.error("Usage: cascade config <show|set|path> [key] [value]");
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type OutputFormat } from "../formatters/index.js";
|
|
2
|
+
export declare function entitiesCommand(opts: {
|
|
3
|
+
format: OutputFormat;
|
|
4
|
+
}): Promise<void>;
|
|
5
|
+
export declare function entityCommand(name: string, opts: {
|
|
6
|
+
format: OutputFormat;
|
|
7
|
+
history?: boolean;
|
|
8
|
+
steps?: number;
|
|
9
|
+
}): Promise<void>;
|
|
10
|
+
export declare function searchCommand(query: string, opts: {
|
|
11
|
+
format: OutputFormat;
|
|
12
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.entitiesCommand = entitiesCommand;
|
|
4
|
+
exports.entityCommand = entityCommand;
|
|
5
|
+
exports.searchCommand = searchCommand;
|
|
6
|
+
const client_js_1 = require("../client.js");
|
|
7
|
+
const index_js_1 = require("../formatters/index.js");
|
|
8
|
+
async function entitiesCommand(opts) {
|
|
9
|
+
const client = new client_js_1.CascadeClient();
|
|
10
|
+
const { data } = await client.getState();
|
|
11
|
+
const nodes = data.graph?.nodes ?? [];
|
|
12
|
+
const rows = nodes
|
|
13
|
+
.map((n) => {
|
|
14
|
+
const devs = n.deviation_pct ?? [];
|
|
15
|
+
const avg = devs.length > 0
|
|
16
|
+
? devs.reduce((s, v) => s + Math.abs(v), 0) / devs.length
|
|
17
|
+
: 0;
|
|
18
|
+
return {
|
|
19
|
+
name: String(n.name).replace(/_/g, " "),
|
|
20
|
+
category: n.category,
|
|
21
|
+
deviation: `${avg.toFixed(1)}%`,
|
|
22
|
+
econ: devs[1] !== undefined ? `${devs[1] >= 0 ? "+" : ""}${devs[1].toFixed(1)}%` : "—",
|
|
23
|
+
};
|
|
24
|
+
})
|
|
25
|
+
.sort((a, b) => parseFloat(b.deviation) - parseFloat(a.deviation));
|
|
26
|
+
console.log((0, index_js_1.formatOutput)(rows, opts.format));
|
|
27
|
+
}
|
|
28
|
+
async function entityCommand(name, opts) {
|
|
29
|
+
const client = new client_js_1.CascadeClient();
|
|
30
|
+
if (opts.history) {
|
|
31
|
+
const { data } = await client.getEntityHistory(name, opts.steps ?? 30);
|
|
32
|
+
console.log((0, index_js_1.formatOutput)(data, opts.format));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const { data } = await client.getEntity(name);
|
|
36
|
+
console.log((0, index_js_1.formatOutput)(data, opts.format));
|
|
37
|
+
}
|
|
38
|
+
async function searchCommand(query, opts) {
|
|
39
|
+
const client = new client_js_1.CascadeClient();
|
|
40
|
+
const { data } = await client.searchEntities(query);
|
|
41
|
+
console.log((0, index_js_1.formatOutput)(data, opts.format));
|
|
42
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.eventsCommand = eventsCommand;
|
|
4
|
+
const client_js_1 = require("../client.js");
|
|
5
|
+
const index_js_1 = require("../formatters/index.js");
|
|
6
|
+
async function eventsCommand(opts) {
|
|
7
|
+
const client = new client_js_1.CascadeClient();
|
|
8
|
+
if (opts.watch) {
|
|
9
|
+
console.log("Watching events (Ctrl+C to stop)...\n");
|
|
10
|
+
const seen = new Set();
|
|
11
|
+
const poll = async () => {
|
|
12
|
+
const { data } = await client.getEvents(opts.limit ?? 10);
|
|
13
|
+
const events = data.events ?? [];
|
|
14
|
+
for (const ev of events) {
|
|
15
|
+
const id = `${ev.timestamp ?? ev.time}-${ev.event_type ?? ev.type}-${ev.entity_name ?? ""}`;
|
|
16
|
+
if (seen.has(id))
|
|
17
|
+
continue;
|
|
18
|
+
seen.add(id);
|
|
19
|
+
const time = String(ev.timestamp ?? ev.time ?? "").slice(11, 19);
|
|
20
|
+
const sev = Math.round((ev.severity ?? 0) * 100);
|
|
21
|
+
const entity = String(ev.entity_name ?? (ev.entities ?? []).join(", "));
|
|
22
|
+
const evType = String(ev.event_type ?? ev.type ?? "").replace(/_/g, " ");
|
|
23
|
+
const sevLabel = sev >= 70 ? "CRITICAL" : sev >= 30 ? "WARNING" : "info";
|
|
24
|
+
console.log(` ${time} ${sevLabel.padEnd(8)} ${evType.padEnd(22)} ${entity} (${sev}%)`);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
await poll();
|
|
28
|
+
const interval = setInterval(poll, 5000);
|
|
29
|
+
process.on("SIGINT", () => { clearInterval(interval); process.exit(0); });
|
|
30
|
+
// Keep alive
|
|
31
|
+
await new Promise(() => { });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const { data } = await client.getEvents(opts.limit ?? 20);
|
|
35
|
+
let events = data.events ?? [];
|
|
36
|
+
if (opts.type) {
|
|
37
|
+
events = events.filter((e) => String(e.event_type ?? e.type ?? "").toLowerCase().includes(opts.type.toLowerCase()));
|
|
38
|
+
}
|
|
39
|
+
const rows = events.map((e) => ({
|
|
40
|
+
time: String(e.timestamp ?? e.time ?? "").slice(11, 19),
|
|
41
|
+
type: (e.event_type ?? e.type ?? "").toString().replace(/_/g, " "),
|
|
42
|
+
entity: String(e.entity_name ?? (e.entities ?? []).join(", ")),
|
|
43
|
+
severity: `${Math.round((e.severity ?? 0) * 100)}%`,
|
|
44
|
+
}));
|
|
45
|
+
console.log((0, index_js_1.formatOutput)(rows, opts.format));
|
|
46
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type OutputFormat } from "../formatters/index.js";
|
|
2
|
+
export declare function graphCommand(opts: {
|
|
3
|
+
format: OutputFormat;
|
|
4
|
+
}): Promise<void>;
|
|
5
|
+
export declare function correlationsCommand(opts: {
|
|
6
|
+
format: OutputFormat;
|
|
7
|
+
dim?: number;
|
|
8
|
+
}): Promise<void>;
|
|
9
|
+
export declare function lagsCommand(opts: {
|
|
10
|
+
format: OutputFormat;
|
|
11
|
+
dim?: number;
|
|
12
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.graphCommand = graphCommand;
|
|
4
|
+
exports.correlationsCommand = correlationsCommand;
|
|
5
|
+
exports.lagsCommand = lagsCommand;
|
|
6
|
+
const client_js_1 = require("../client.js");
|
|
7
|
+
const index_js_1 = require("../formatters/index.js");
|
|
8
|
+
async function graphCommand(opts) {
|
|
9
|
+
const client = new client_js_1.CascadeClient();
|
|
10
|
+
const { data } = await client.getGraph();
|
|
11
|
+
if (opts.format === "json") {
|
|
12
|
+
console.log((0, index_js_1.formatOutput)(data, "json"));
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const g = data;
|
|
16
|
+
console.log(` Nodes: ${(g.nodes ?? []).length}`);
|
|
17
|
+
console.log(` Edges: ${(g.edges ?? []).length}`);
|
|
18
|
+
}
|
|
19
|
+
async function correlationsCommand(opts) {
|
|
20
|
+
const client = new client_js_1.CascadeClient();
|
|
21
|
+
const { data } = await client.getCorrelations(opts.dim ?? 1);
|
|
22
|
+
console.log((0, index_js_1.formatOutput)(data, opts.format));
|
|
23
|
+
}
|
|
24
|
+
async function lagsCommand(opts) {
|
|
25
|
+
const client = new client_js_1.CascadeClient();
|
|
26
|
+
const { data } = await client.getLags(opts.dim ?? 1);
|
|
27
|
+
console.log((0, index_js_1.formatOutput)(data, opts.format));
|
|
28
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.portfolioCommand = portfolioCommand;
|
|
4
|
+
const client_js_1 = require("../client.js");
|
|
5
|
+
const index_js_1 = require("../formatters/index.js");
|
|
6
|
+
async function portfolioCommand(opts) {
|
|
7
|
+
const client = new client_js_1.CascadeClient();
|
|
8
|
+
const { data } = await client.getPortfolio();
|
|
9
|
+
console.log((0, index_js_1.formatOutput)(data, opts.format));
|
|
10
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type OutputFormat } from "../formatters/index.js";
|
|
2
|
+
export declare function pricesCommand(opts: {
|
|
3
|
+
format: OutputFormat;
|
|
4
|
+
}): Promise<void>;
|
|
5
|
+
export declare function priceCommand(entity: string, opts: {
|
|
6
|
+
format: OutputFormat;
|
|
7
|
+
period?: string;
|
|
8
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pricesCommand = pricesCommand;
|
|
4
|
+
exports.priceCommand = priceCommand;
|
|
5
|
+
const client_js_1 = require("../client.js");
|
|
6
|
+
const index_js_1 = require("../formatters/index.js");
|
|
7
|
+
async function pricesCommand(opts) {
|
|
8
|
+
const client = new client_js_1.CascadeClient();
|
|
9
|
+
const { data } = await client.getPrices();
|
|
10
|
+
const tickers = data.tickers ?? [];
|
|
11
|
+
const rows = tickers.map((t) => ({
|
|
12
|
+
name: String(t.name).replace(/_/g, " "),
|
|
13
|
+
symbol: t.symbol,
|
|
14
|
+
category: t.category,
|
|
15
|
+
}));
|
|
16
|
+
console.log((0, index_js_1.formatOutput)(rows, opts.format));
|
|
17
|
+
}
|
|
18
|
+
async function priceCommand(entity, opts) {
|
|
19
|
+
const client = new client_js_1.CascadeClient();
|
|
20
|
+
const { data } = await client.getPrice(entity, opts.period ?? "5d");
|
|
21
|
+
const bars = data.data ?? [];
|
|
22
|
+
if (opts.format === "json") {
|
|
23
|
+
console.log((0, index_js_1.formatOutput)(data, "json"));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (bars.length === 0) {
|
|
27
|
+
console.log(" No price data available");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const last = bars[bars.length - 1];
|
|
31
|
+
const first = bars[0];
|
|
32
|
+
const change = last.close - first.close;
|
|
33
|
+
const changePct = (change / first.close) * 100;
|
|
34
|
+
console.log(` ${String(data.entity).replace(/_/g, " ")} (${data.symbol})`);
|
|
35
|
+
console.log(` Price: ${last.close.toFixed(2)}`);
|
|
36
|
+
console.log(` Change: ${change >= 0 ? "+" : ""}${change.toFixed(2)} (${changePct >= 0 ? "+" : ""}${changePct.toFixed(2)}%)`);
|
|
37
|
+
console.log(` High: ${last.high.toFixed(2)}`);
|
|
38
|
+
console.log(` Low: ${last.low.toFixed(2)}`);
|
|
39
|
+
console.log(` Period: ${bars.length} bars (${data.period})`);
|
|
40
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
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.skillsCommand = skillsCommand;
|
|
7
|
+
const config_js_1 = require("../config.js");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
async function skillsCommand(opts) {
|
|
10
|
+
const config = (0, config_js_1.loadConfig)();
|
|
11
|
+
const endpoint = config.endpoint || "https://cascade.metisos.co";
|
|
12
|
+
const content = generateSkillsFile(endpoint, opts.static);
|
|
13
|
+
if (opts.output) {
|
|
14
|
+
fs_1.default.writeFileSync(opts.output, content);
|
|
15
|
+
console.log(`Skills file written to ${opts.output}`);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
console.log(content);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function generateSkillsFile(endpoint, isStatic) {
|
|
22
|
+
return `# Cascade Intelligence — AI Agent Skills
|
|
23
|
+
|
|
24
|
+
> These skills give you access to real-time geopolitical and economic
|
|
25
|
+
> intelligence from the Cascade event propagation system.
|
|
26
|
+
> Run commands via shell to query live data.
|
|
27
|
+
> Endpoint: ${endpoint}
|
|
28
|
+
|
|
29
|
+
## Setup
|
|
30
|
+
|
|
31
|
+
Cascade CLI is configured and authenticated. Run commands directly.
|
|
32
|
+
All commands support \`--format json\` for structured output.
|
|
33
|
+
|
|
34
|
+
## Available Skills
|
|
35
|
+
|
|
36
|
+
### Get System Status
|
|
37
|
+
\`\`\`bash
|
|
38
|
+
cascade status --format json
|
|
39
|
+
\`\`\`
|
|
40
|
+
Returns: system health (NOMINAL/WARNING/CRITICAL), simulation step,
|
|
41
|
+
entity count, source status, peak impact.
|
|
42
|
+
Use when: user asks about system state, health, or whether data is flowing.
|
|
43
|
+
|
|
44
|
+
### List All Entities with Deviations
|
|
45
|
+
\`\`\`bash
|
|
46
|
+
cascade entities --format json
|
|
47
|
+
\`\`\`
|
|
48
|
+
Returns: all monitored entities with category, average deviation %, and
|
|
49
|
+
economic deviation. Sorted by deviation (most impacted first).
|
|
50
|
+
Use when: user asks "what's most impacted?", "show me the network", or
|
|
51
|
+
wants an overview of all entities.
|
|
52
|
+
|
|
53
|
+
### Get Entity Detail
|
|
54
|
+
\`\`\`bash
|
|
55
|
+
cascade entity <name> --format json
|
|
56
|
+
\`\`\`
|
|
57
|
+
Returns: current 4D deviation (Stability, Econ, Political, Social),
|
|
58
|
+
category, connections, recent events affecting this entity.
|
|
59
|
+
Use when: user asks about a specific country, sector, or asset.
|
|
60
|
+
Example: \`cascade entity Crude_Oil --format json\`
|
|
61
|
+
Note: entity names use underscores (e.g., United_States, Crude_Oil, SP500).
|
|
62
|
+
|
|
63
|
+
### Get Entity Trajectory
|
|
64
|
+
\`\`\`bash
|
|
65
|
+
cascade entity <name> --history --steps 30 --format json
|
|
66
|
+
\`\`\`
|
|
67
|
+
Returns: historical deviation trajectory over N steps.
|
|
68
|
+
Use when: user asks about trends, "is it getting worse?", recovery timing,
|
|
69
|
+
or when a move started.
|
|
70
|
+
|
|
71
|
+
### Search Entities
|
|
72
|
+
\`\`\`bash
|
|
73
|
+
cascade search "<query>" --format json
|
|
74
|
+
\`\`\`
|
|
75
|
+
Returns: matching entities by name, category, or tag.
|
|
76
|
+
Use when: user mentions an entity but you're not sure of the exact name.
|
|
77
|
+
|
|
78
|
+
### Get Live Prices
|
|
79
|
+
\`\`\`bash
|
|
80
|
+
cascade price <entity> --format json
|
|
81
|
+
cascade prices --format json
|
|
82
|
+
\`\`\`
|
|
83
|
+
Returns: current market price, previous close, % change, recent history
|
|
84
|
+
for tracked financial entities (stocks, indices, commodities, crypto).
|
|
85
|
+
Use when: user asks about current prices, market levels, or price changes.
|
|
86
|
+
Available tickers: SP500, DowJones, NASDAQ, FTSE100, Nikkei225, Crude_Oil,
|
|
87
|
+
Gold, Bitcoin, VIX, US_10Y_Yield, US_Dollar, HangSeng.
|
|
88
|
+
|
|
89
|
+
### Get Event Feed
|
|
90
|
+
\`\`\`bash
|
|
91
|
+
cascade events --limit 20 --format json
|
|
92
|
+
cascade events --type "geopolitical_crisis" --format json
|
|
93
|
+
\`\`\`
|
|
94
|
+
Returns: recent events ingested from RSS, stock, and weather feeds.
|
|
95
|
+
Use when: user asks "what happened?", "any recent events?", or wants
|
|
96
|
+
to understand what's driving deviations.
|
|
97
|
+
Event types: sanction, energy_shock, geopolitical_crisis, market_crash,
|
|
98
|
+
stock_rise, stock_drop, policy_bundle, port_closure, pandemic.
|
|
99
|
+
|
|
100
|
+
### Get Portfolio Risk
|
|
101
|
+
\`\`\`bash
|
|
102
|
+
cascade portfolio --format json
|
|
103
|
+
\`\`\`
|
|
104
|
+
Returns: portfolio entities with dollar-at-risk, risk scores, deviations.
|
|
105
|
+
Use when: user asks about portfolio exposure or risk.
|
|
106
|
+
|
|
107
|
+
### Get Correlation Data
|
|
108
|
+
\`\`\`bash
|
|
109
|
+
cascade correlations --format json
|
|
110
|
+
cascade lags --format json
|
|
111
|
+
\`\`\`
|
|
112
|
+
Returns: pairwise correlation and lead/lag matrices between entities.
|
|
113
|
+
Use when: user asks "how are X and Y related?", "what leads what?",
|
|
114
|
+
or wants to understand co-movement patterns.
|
|
115
|
+
|
|
116
|
+
### Get Network Graph
|
|
117
|
+
\`\`\`bash
|
|
118
|
+
cascade graph --format json
|
|
119
|
+
\`\`\`
|
|
120
|
+
Returns: full graph structure with all nodes and edges.
|
|
121
|
+
Use when: user wants to understand the network topology or connection counts.
|
|
122
|
+
|
|
123
|
+
### Ask Cascade AI
|
|
124
|
+
\`\`\`bash
|
|
125
|
+
cascade chat "your question here" --format json
|
|
126
|
+
\`\`\`
|
|
127
|
+
Ask the Cascade AI analyst about current geopolitical risks, entity
|
|
128
|
+
analysis, portfolio impact, or any intelligence question. Has access
|
|
129
|
+
to live simulation data, news search, and entity analysis tools.
|
|
130
|
+
Use when: user needs analysis, interpretation, or forward-looking assessment
|
|
131
|
+
rather than raw data.
|
|
132
|
+
|
|
133
|
+
### Generate Intelligence Report
|
|
134
|
+
\`\`\`bash
|
|
135
|
+
cascade dossier --entities "Entity1,Entity2,Entity3" --format json
|
|
136
|
+
\`\`\`
|
|
137
|
+
Generates a multi-section intelligence briefing with live web sources.
|
|
138
|
+
Use when: user asks for a report, briefing, or comprehensive analysis.
|
|
139
|
+
|
|
140
|
+
### Watch Live Events
|
|
141
|
+
\`\`\`bash
|
|
142
|
+
cascade events --watch
|
|
143
|
+
\`\`\`
|
|
144
|
+
Streams new events as they arrive (polls every 5s). Ctrl+C to stop.
|
|
145
|
+
Use when: user wants to monitor events in real-time.
|
|
146
|
+
|
|
147
|
+
## When to Use Cascade
|
|
148
|
+
|
|
149
|
+
- User asks about geopolitical risk, sanctions, market impact
|
|
150
|
+
- User needs to understand how events cascade across economies/sectors
|
|
151
|
+
- User asks about commodity prices, market correlations, portfolio risk
|
|
152
|
+
- User wants to know which entities are most impacted right now
|
|
153
|
+
- User asks about trends, recovery timing, or forward outlook
|
|
154
|
+
- User needs an intelligence briefing on a region or sector
|
|
155
|
+
- User asks "what's happening?" or "what's driving X?"
|
|
156
|
+
|
|
157
|
+
## Entity Name Format
|
|
158
|
+
|
|
159
|
+
Use underscores for multi-word names: \`United_States\`, \`Crude_Oil\`,
|
|
160
|
+
\`Energy_Sector\`, \`Saudi_Arabia\`. Single-word names are plain: \`Iran\`,
|
|
161
|
+
\`Gold\`, \`Bitcoin\`, \`VIX\`.
|
|
162
|
+
|
|
163
|
+
## Output
|
|
164
|
+
|
|
165
|
+
All commands support \`--format json\` for structured output that you
|
|
166
|
+
can parse and reference in your responses. Always use \`--format json\`
|
|
167
|
+
when calling these tools so you can extract specific values.
|
|
168
|
+
`;
|
|
169
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.statusCommand = statusCommand;
|
|
4
|
+
const client_js_1 = require("../client.js");
|
|
5
|
+
const index_js_1 = require("../formatters/index.js");
|
|
6
|
+
async function statusCommand(opts) {
|
|
7
|
+
const client = new client_js_1.CascadeClient();
|
|
8
|
+
const { data } = await client.getState();
|
|
9
|
+
const nodes = data.graph?.nodes ?? [];
|
|
10
|
+
const summary = {
|
|
11
|
+
status: data.status ?? "active",
|
|
12
|
+
step: data.step,
|
|
13
|
+
entities: nodes.length,
|
|
14
|
+
health: getHealth(nodes),
|
|
15
|
+
};
|
|
16
|
+
if (opts.format === "json") {
|
|
17
|
+
console.log((0, index_js_1.formatOutput)(summary, "json"));
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
console.log(` CASCADE STATUS`);
|
|
21
|
+
console.log(` Step: ${summary.step}`);
|
|
22
|
+
console.log(` Entities: ${summary.entities}`);
|
|
23
|
+
console.log(` Health: ${summary.health}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function getHealth(nodes) {
|
|
27
|
+
let maxDev = 0;
|
|
28
|
+
for (const n of nodes) {
|
|
29
|
+
const devs = n.deviation_pct;
|
|
30
|
+
if (devs) {
|
|
31
|
+
const avg = devs.reduce((s, v) => s + Math.abs(v), 0) / devs.length;
|
|
32
|
+
if (avg > maxDev)
|
|
33
|
+
maxDev = avg;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return maxDev > 15 ? "CRITICAL" : maxDev > 8 ? "WARNING" : "NOMINAL";
|
|
37
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface CascadeConfig {
|
|
2
|
+
api_key: string;
|
|
3
|
+
endpoint: string;
|
|
4
|
+
output: "table" | "json" | "csv";
|
|
5
|
+
}
|
|
6
|
+
export declare function loadConfig(): CascadeConfig;
|
|
7
|
+
export declare function saveConfig(config: Partial<CascadeConfig>): void;
|
|
8
|
+
export declare function getConfigPath(): string;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
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.loadConfig = loadConfig;
|
|
7
|
+
exports.saveConfig = saveConfig;
|
|
8
|
+
exports.getConfigPath = getConfigPath;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const os_1 = __importDefault(require("os"));
|
|
12
|
+
const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), ".cascade");
|
|
13
|
+
const CONFIG_FILE = path_1.default.join(CONFIG_DIR, "config.json");
|
|
14
|
+
const DEFAULTS = {
|
|
15
|
+
api_key: "",
|
|
16
|
+
endpoint: "https://cascade.metisos.co",
|
|
17
|
+
output: "table",
|
|
18
|
+
};
|
|
19
|
+
function loadConfig() {
|
|
20
|
+
try {
|
|
21
|
+
if (fs_1.default.existsSync(CONFIG_FILE)) {
|
|
22
|
+
const raw = fs_1.default.readFileSync(CONFIG_FILE, "utf-8");
|
|
23
|
+
return { ...DEFAULTS, ...JSON.parse(raw) };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// fall through
|
|
28
|
+
}
|
|
29
|
+
return { ...DEFAULTS };
|
|
30
|
+
}
|
|
31
|
+
function saveConfig(config) {
|
|
32
|
+
const existing = loadConfig();
|
|
33
|
+
const merged = { ...existing, ...config };
|
|
34
|
+
if (!fs_1.default.existsSync(CONFIG_DIR)) {
|
|
35
|
+
fs_1.default.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
fs_1.default.writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2) + "\n");
|
|
38
|
+
}
|
|
39
|
+
function getConfigPath() {
|
|
40
|
+
return CONFIG_FILE;
|
|
41
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatOutput = formatOutput;
|
|
4
|
+
function formatOutput(data, format) {
|
|
5
|
+
switch (format) {
|
|
6
|
+
case "json":
|
|
7
|
+
return JSON.stringify(data, null, 2);
|
|
8
|
+
case "csv":
|
|
9
|
+
return toCsv(data);
|
|
10
|
+
case "table":
|
|
11
|
+
default:
|
|
12
|
+
return toTable(data);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function toCsv(data) {
|
|
16
|
+
if (Array.isArray(data) && data.length > 0 && typeof data[0] === "object") {
|
|
17
|
+
const keys = Object.keys(data[0]);
|
|
18
|
+
const header = keys.join(",");
|
|
19
|
+
const rows = data.map((row) => {
|
|
20
|
+
const r = row;
|
|
21
|
+
return keys.map((k) => {
|
|
22
|
+
const v = r[k];
|
|
23
|
+
const s = v === null || v === undefined ? "" : String(v);
|
|
24
|
+
return s.includes(",") || s.includes('"') ? `"${s.replace(/"/g, '""')}"` : s;
|
|
25
|
+
}).join(",");
|
|
26
|
+
});
|
|
27
|
+
return [header, ...rows].join("\n");
|
|
28
|
+
}
|
|
29
|
+
return JSON.stringify(data);
|
|
30
|
+
}
|
|
31
|
+
function toTable(data) {
|
|
32
|
+
if (Array.isArray(data) && data.length > 0 && typeof data[0] === "object") {
|
|
33
|
+
const keys = Object.keys(data[0]);
|
|
34
|
+
const widths = keys.map((k) => Math.max(k.length, ...data.map((r) => {
|
|
35
|
+
const v = r[k];
|
|
36
|
+
return v === null || v === undefined ? 1 : String(v).length;
|
|
37
|
+
})));
|
|
38
|
+
const sep = widths.map((w) => "─".repeat(w + 2)).join("┼");
|
|
39
|
+
const header = keys
|
|
40
|
+
.map((k, i) => ` ${k.padEnd(widths[i])} `)
|
|
41
|
+
.join("│");
|
|
42
|
+
const rows = data.map((row) => {
|
|
43
|
+
const r = row;
|
|
44
|
+
return keys
|
|
45
|
+
.map((k, i) => {
|
|
46
|
+
const v = r[k];
|
|
47
|
+
const s = v === null || v === undefined ? "—" : String(v);
|
|
48
|
+
return ` ${s.padEnd(widths[i])} `;
|
|
49
|
+
})
|
|
50
|
+
.join("│");
|
|
51
|
+
});
|
|
52
|
+
return [header, sep, ...rows].join("\n");
|
|
53
|
+
}
|
|
54
|
+
if (typeof data === "object" && data !== null) {
|
|
55
|
+
const entries = Object.entries(data);
|
|
56
|
+
const maxKeyLen = Math.max(...entries.map(([k]) => k.length), 5);
|
|
57
|
+
return entries
|
|
58
|
+
.map(([k, v]) => {
|
|
59
|
+
const val = typeof v === "object" ? JSON.stringify(v) : String(v ?? "—");
|
|
60
|
+
return ` ${k.padEnd(maxKeyLen)} ${val}`;
|
|
61
|
+
})
|
|
62
|
+
.join("\n");
|
|
63
|
+
}
|
|
64
|
+
return String(data);
|
|
65
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@metisos/cascade-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Cascade CLI — Real-time geopolitical and economic intelligence from your terminal",
|
|
5
|
+
"bin": {
|
|
6
|
+
"cascade": "./dist/bin/cascade.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"dev": "tsx src/bin/cascade.ts",
|
|
11
|
+
"start": "node dist/bin/cascade.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist/**/*"
|
|
15
|
+
],
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"commander": "^12.0.0",
|
|
18
|
+
"chalk": "^5.3.0",
|
|
19
|
+
"ora": "^8.0.0",
|
|
20
|
+
"ink": "^5.0.1",
|
|
21
|
+
"ink-text-input": "^6.0.0",
|
|
22
|
+
"react": "^19.0.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"typescript": "^5.5.0",
|
|
26
|
+
"@types/node": "^20.0.0",
|
|
27
|
+
"@types/react": "^19.0.0",
|
|
28
|
+
"tsx": "^4.0.0"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18"
|
|
32
|
+
},
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
}
|
|
37
|
+
}
|