coherence-cli 0.7.1 → 0.8.1
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/bin/cc.mjs +196 -107
- package/lib/commands/assets.mjs +73 -0
- package/lib/commands/contributors.mjs +79 -0
- package/lib/commands/diagnostics.mjs +131 -0
- package/lib/commands/friction.mjs +86 -0
- package/lib/commands/governance.mjs +90 -0
- package/lib/commands/lineage.mjs +101 -0
- package/lib/commands/news.mjs +121 -0
- package/lib/commands/providers.mjs +57 -0
- package/lib/commands/services.mjs +106 -0
- package/lib/commands/traceability.mjs +107 -0
- package/lib/commands/treasury.mjs +69 -0
- package/package.json +1 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Friction commands: friction, friction events, friction categories
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { get } from "../api.mjs";
|
|
6
|
+
|
|
7
|
+
function truncate(str, len) {
|
|
8
|
+
if (!str) return "";
|
|
9
|
+
return str.length > len ? str.slice(0, len - 1) + "\u2026" : str;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function showFrictionReport() {
|
|
13
|
+
const data = await get("/api/friction/report");
|
|
14
|
+
if (!data) {
|
|
15
|
+
console.log("Could not fetch friction report.");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
console.log();
|
|
20
|
+
console.log(`\x1b[1m FRICTION REPORT\x1b[0m`);
|
|
21
|
+
console.log(` ${"─".repeat(50)}`);
|
|
22
|
+
if (data.total_friction != null) console.log(` Total: ${data.total_friction}`);
|
|
23
|
+
if (data.top_categories && Array.isArray(data.top_categories)) {
|
|
24
|
+
console.log(` Top categories:`);
|
|
25
|
+
for (const c of data.top_categories) {
|
|
26
|
+
const name = c.name || c.category || "?";
|
|
27
|
+
const count = c.count != null ? c.count : "?";
|
|
28
|
+
console.log(` ${name.padEnd(25)} ${count}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Fallback: print all keys
|
|
32
|
+
const shown = new Set(["total_friction", "top_categories"]);
|
|
33
|
+
for (const [key, val] of Object.entries(data)) {
|
|
34
|
+
if (!shown.has(key) && val != null) {
|
|
35
|
+
console.log(` ${key}: ${JSON.stringify(val)}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
console.log();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function listFrictionEvents(args) {
|
|
42
|
+
const limit = parseInt(args[0]) || 20;
|
|
43
|
+
const raw = await get("/api/friction/events", { limit });
|
|
44
|
+
const data = Array.isArray(raw) ? raw : raw?.events;
|
|
45
|
+
if (!data || !Array.isArray(data)) {
|
|
46
|
+
console.log("Could not fetch friction events.");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (data.length === 0) {
|
|
50
|
+
console.log("No friction events found.");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log();
|
|
55
|
+
console.log(`\x1b[1m FRICTION EVENTS\x1b[0m (${data.length})`);
|
|
56
|
+
console.log(` ${"─".repeat(60)}`);
|
|
57
|
+
for (const e of data) {
|
|
58
|
+
const desc = truncate(e.description || e.type || e.id || "?", 45);
|
|
59
|
+
const category = (e.category || "").padEnd(15);
|
|
60
|
+
console.log(` ${category} ${desc}`);
|
|
61
|
+
}
|
|
62
|
+
console.log();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function showFrictionCategories() {
|
|
66
|
+
const raw = await get("/api/friction/categories");
|
|
67
|
+
const data = Array.isArray(raw) ? raw : raw?.categories;
|
|
68
|
+
if (!data || !Array.isArray(data)) {
|
|
69
|
+
console.log("Could not fetch friction categories.");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (data.length === 0) {
|
|
73
|
+
console.log("No friction categories found.");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log();
|
|
78
|
+
console.log(`\x1b[1m FRICTION CATEGORIES\x1b[0m (${data.length})`);
|
|
79
|
+
console.log(` ${"─".repeat(50)}`);
|
|
80
|
+
for (const c of data) {
|
|
81
|
+
const name = typeof c === "string" ? c : c.name || c.category || "?";
|
|
82
|
+
const count = typeof c === "object" && c.count != null ? `(${c.count})` : "";
|
|
83
|
+
console.log(` ${name} ${count}`);
|
|
84
|
+
}
|
|
85
|
+
console.log();
|
|
86
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Governance commands: governance, governance vote, governance propose
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { get, post } from "../api.mjs";
|
|
6
|
+
|
|
7
|
+
function truncate(str, len) {
|
|
8
|
+
if (!str) return "";
|
|
9
|
+
return str.length > len ? str.slice(0, len - 1) + "\u2026" : str;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function listChangeRequests() {
|
|
13
|
+
const raw = await get("/api/governance/change-requests");
|
|
14
|
+
const data = Array.isArray(raw) ? raw : raw?.change_requests || raw?.items;
|
|
15
|
+
if (!data || !Array.isArray(data)) {
|
|
16
|
+
console.log("Could not fetch change requests.");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (data.length === 0) {
|
|
20
|
+
console.log("No change requests found.");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log();
|
|
25
|
+
console.log(`\x1b[1m GOVERNANCE\x1b[0m (${data.length} change requests)`);
|
|
26
|
+
console.log(` ${"─".repeat(60)}`);
|
|
27
|
+
for (const cr of data) {
|
|
28
|
+
const status = cr.status || "?";
|
|
29
|
+
const dot = status === "approved" ? "\x1b[32m●\x1b[0m" : status === "rejected" ? "\x1b[31m●\x1b[0m" : "\x1b[33m●\x1b[0m";
|
|
30
|
+
const title = truncate(cr.title || cr.id, 45);
|
|
31
|
+
console.log(` ${dot} ${title.padEnd(47)} ${status}`);
|
|
32
|
+
}
|
|
33
|
+
console.log();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function showChangeRequest(args) {
|
|
37
|
+
const id = args[0];
|
|
38
|
+
if (!id) {
|
|
39
|
+
console.log("Usage: cc governance <id>");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const data = await get(`/api/governance/change-requests/${encodeURIComponent(id)}`);
|
|
43
|
+
if (!data) {
|
|
44
|
+
console.log(`Change request '${id}' not found.`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log();
|
|
49
|
+
console.log(`\x1b[1m ${data.title || data.id}\x1b[0m`);
|
|
50
|
+
console.log(` ${"─".repeat(50)}`);
|
|
51
|
+
if (data.id) console.log(` ID: ${data.id}`);
|
|
52
|
+
if (data.status) console.log(` Status: ${data.status}`);
|
|
53
|
+
if (data.description) console.log(` Description: ${truncate(data.description, 60)}`);
|
|
54
|
+
if (data.votes_for != null) console.log(` Votes For: ${data.votes_for}`);
|
|
55
|
+
if (data.votes_against != null) console.log(` Against: ${data.votes_against}`);
|
|
56
|
+
if (data.created_at) console.log(` Created: ${data.created_at}`);
|
|
57
|
+
console.log();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function vote(args) {
|
|
61
|
+
const id = args[0];
|
|
62
|
+
const choice = args[1];
|
|
63
|
+
if (!id || !choice || !["yes", "no"].includes(choice)) {
|
|
64
|
+
console.log("Usage: cc governance vote <id> <yes|no>");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const result = await post(`/api/governance/change-requests/${encodeURIComponent(id)}/votes`, {
|
|
68
|
+
vote: choice,
|
|
69
|
+
});
|
|
70
|
+
if (result) {
|
|
71
|
+
console.log(`\x1b[32m✓\x1b[0m Vote '${choice}' recorded on ${id}`);
|
|
72
|
+
} else {
|
|
73
|
+
console.log("Vote failed.");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function propose(args) {
|
|
78
|
+
const title = args[0];
|
|
79
|
+
const desc = args.slice(1).join(" ");
|
|
80
|
+
if (!title || !desc) {
|
|
81
|
+
console.log("Usage: cc governance propose <title> <description>");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const result = await post("/api/governance/change-requests", { title, description: desc });
|
|
85
|
+
if (result) {
|
|
86
|
+
console.log(`\x1b[32m✓\x1b[0m Proposal created: ${result.id || title}`);
|
|
87
|
+
} else {
|
|
88
|
+
console.log("Failed to create proposal.");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lineage commands: lineage, lineage valuation, lineage payout
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { get, post } from "../api.mjs";
|
|
6
|
+
|
|
7
|
+
function truncate(str, len) {
|
|
8
|
+
if (!str) return "";
|
|
9
|
+
return str.length > len ? str.slice(0, len - 1) + "\u2026" : str;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function listLinks(args) {
|
|
13
|
+
const limit = parseInt(args[0]) || 20;
|
|
14
|
+
const raw = await get("/api/value-lineage/links", { limit });
|
|
15
|
+
const data = Array.isArray(raw) ? raw : raw?.links;
|
|
16
|
+
if (!data || !Array.isArray(data)) {
|
|
17
|
+
console.log("Could not fetch lineage links.");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (data.length === 0) {
|
|
21
|
+
console.log("No lineage links found.");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log();
|
|
26
|
+
console.log(`\x1b[1m VALUE LINEAGE\x1b[0m (${data.length})`);
|
|
27
|
+
console.log(` ${"─".repeat(60)}`);
|
|
28
|
+
for (const link of data) {
|
|
29
|
+
const from = truncate(link.from_id || link.source || "?", 20);
|
|
30
|
+
const to = truncate(link.to_id || link.target || "?", 20);
|
|
31
|
+
const weight = link.weight != null ? `w=${link.weight.toFixed(2)}` : "";
|
|
32
|
+
console.log(` ${from.padEnd(22)} \x1b[2m→\x1b[0m ${to.padEnd(22)} ${weight}`);
|
|
33
|
+
}
|
|
34
|
+
console.log();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function showLink(args) {
|
|
38
|
+
const id = args[0];
|
|
39
|
+
if (!id) {
|
|
40
|
+
console.log("Usage: cc lineage <id>");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const data = await get(`/api/value-lineage/links/${encodeURIComponent(id)}`);
|
|
44
|
+
if (!data) {
|
|
45
|
+
console.log(`Link '${id}' not found.`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log();
|
|
50
|
+
console.log(`\x1b[1m LINEAGE LINK\x1b[0m ${data.id || id}`);
|
|
51
|
+
console.log(` ${"─".repeat(50)}`);
|
|
52
|
+
if (data.from_id || data.source) console.log(` From: ${data.from_id || data.source}`);
|
|
53
|
+
if (data.to_id || data.target) console.log(` To: ${data.to_id || data.target}`);
|
|
54
|
+
if (data.weight != null) console.log(` Weight: ${data.weight}`);
|
|
55
|
+
if (data.type) console.log(` Type: ${data.type}`);
|
|
56
|
+
if (data.created_at) console.log(` Created: ${data.created_at}`);
|
|
57
|
+
console.log();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function showValuation(args) {
|
|
61
|
+
const id = args[0];
|
|
62
|
+
if (!id) {
|
|
63
|
+
console.log("Usage: cc lineage <id> valuation");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const data = await get(`/api/value-lineage/links/${encodeURIComponent(id)}/valuation`);
|
|
67
|
+
if (!data) {
|
|
68
|
+
console.log(`No valuation for link '${id}'.`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log();
|
|
73
|
+
console.log(`\x1b[1m VALUATION\x1b[0m for ${id}`);
|
|
74
|
+
console.log(` ${"─".repeat(50)}`);
|
|
75
|
+
for (const [key, val] of Object.entries(data)) {
|
|
76
|
+
console.log(` ${key}: ${JSON.stringify(val)}`);
|
|
77
|
+
}
|
|
78
|
+
console.log();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function payoutPreview(args) {
|
|
82
|
+
const id = args[0];
|
|
83
|
+
const amount = parseFloat(args[1]);
|
|
84
|
+
if (!id || isNaN(amount)) {
|
|
85
|
+
console.log("Usage: cc lineage <id> payout <amount>");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const data = await post(`/api/value-lineage/links/${encodeURIComponent(id)}/payout-preview`, { amount });
|
|
89
|
+
if (!data) {
|
|
90
|
+
console.log("Payout preview failed.");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.log();
|
|
95
|
+
console.log(`\x1b[1m PAYOUT PREVIEW\x1b[0m for ${id}`);
|
|
96
|
+
console.log(` ${"─".repeat(50)}`);
|
|
97
|
+
for (const [key, val] of Object.entries(data)) {
|
|
98
|
+
console.log(` ${key}: ${JSON.stringify(val)}`);
|
|
99
|
+
}
|
|
100
|
+
console.log();
|
|
101
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* News commands: news, news trending, news sources, news resonance
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { get, post } from "../api.mjs";
|
|
6
|
+
|
|
7
|
+
function truncate(str, len) {
|
|
8
|
+
if (!str) return "";
|
|
9
|
+
return str.length > len ? str.slice(0, len - 1) + "\u2026" : str;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function showNewsFeed() {
|
|
13
|
+
const data = await get("/api/news/feed");
|
|
14
|
+
const items = Array.isArray(data) ? data : data?.items || data?.articles;
|
|
15
|
+
if (!items || !Array.isArray(items)) {
|
|
16
|
+
console.log("Could not fetch news feed.");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (items.length === 0) {
|
|
20
|
+
console.log("No news items.");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log();
|
|
25
|
+
console.log(`\x1b[1m NEWS FEED\x1b[0m (${items.length})`);
|
|
26
|
+
console.log(` ${"─".repeat(60)}`);
|
|
27
|
+
for (const item of items) {
|
|
28
|
+
const title = truncate(item.title || item.headline || "?", 55);
|
|
29
|
+
const source = item.source ? `\x1b[2m${truncate(item.source, 15)}\x1b[0m` : "";
|
|
30
|
+
console.log(` ${title} ${source}`);
|
|
31
|
+
}
|
|
32
|
+
console.log();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function showTrending() {
|
|
36
|
+
const data = await get("/api/news/trending");
|
|
37
|
+
const items = Array.isArray(data) ? data : data?.trending || data?.items;
|
|
38
|
+
if (!items || !Array.isArray(items)) {
|
|
39
|
+
console.log("Could not fetch trending news.");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (items.length === 0) {
|
|
43
|
+
console.log("Nothing trending right now.");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log();
|
|
48
|
+
console.log(`\x1b[1m TRENDING\x1b[0m (${items.length})`);
|
|
49
|
+
console.log(` ${"─".repeat(60)}`);
|
|
50
|
+
for (const item of items) {
|
|
51
|
+
const title = truncate(item.title || item.headline || "?", 55);
|
|
52
|
+
console.log(` \x1b[33m~\x1b[0m ${title}`);
|
|
53
|
+
}
|
|
54
|
+
console.log();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function showSources() {
|
|
58
|
+
const data = await get("/api/news/sources");
|
|
59
|
+
const sources = Array.isArray(data) ? data : data?.sources;
|
|
60
|
+
if (!sources || !Array.isArray(sources)) {
|
|
61
|
+
console.log("Could not fetch news sources.");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (sources.length === 0) {
|
|
65
|
+
console.log("No news sources configured.");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log();
|
|
70
|
+
console.log(`\x1b[1m NEWS SOURCES\x1b[0m (${sources.length})`);
|
|
71
|
+
console.log(` ${"─".repeat(60)}`);
|
|
72
|
+
for (const s of sources) {
|
|
73
|
+
const name = truncate(s.name || s.url || "?", 30);
|
|
74
|
+
const url = s.url ? `\x1b[2m${truncate(s.url, 40)}\x1b[0m` : "";
|
|
75
|
+
console.log(` ${name.padEnd(32)} ${url}`);
|
|
76
|
+
}
|
|
77
|
+
console.log();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function addSource(args) {
|
|
81
|
+
const url = args[0];
|
|
82
|
+
const name = args.slice(1).join(" ");
|
|
83
|
+
if (!url || !name) {
|
|
84
|
+
console.log("Usage: cc news source add <url> <name>");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const result = await post("/api/news/sources", { url, name });
|
|
88
|
+
if (result) {
|
|
89
|
+
console.log(`\x1b[32m✓\x1b[0m Source added: ${result.name || name}`);
|
|
90
|
+
} else {
|
|
91
|
+
console.log("Failed to add source.");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function showNewsResonance(args) {
|
|
96
|
+
const contributor = args[0];
|
|
97
|
+
const path = contributor
|
|
98
|
+
? `/api/news/resonance/${encodeURIComponent(contributor)}`
|
|
99
|
+
: "/api/news/resonance";
|
|
100
|
+
const data = await get(path);
|
|
101
|
+
if (!data) {
|
|
102
|
+
console.log("Could not fetch news resonance.");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log();
|
|
107
|
+
console.log(`\x1b[1m NEWS RESONANCE\x1b[0m`);
|
|
108
|
+
console.log(` ${"─".repeat(50)}`);
|
|
109
|
+
if (Array.isArray(data)) {
|
|
110
|
+
for (const item of data.slice(0, 15)) {
|
|
111
|
+
const name = item.title || item.topic || item.id || "?";
|
|
112
|
+
const score = item.score != null ? `\x1b[32m${item.score.toFixed(2)}\x1b[0m` : "";
|
|
113
|
+
console.log(` ${truncate(name, 45).padEnd(47)} ${score}`);
|
|
114
|
+
}
|
|
115
|
+
} else if (typeof data === "object") {
|
|
116
|
+
for (const [key, val] of Object.entries(data)) {
|
|
117
|
+
console.log(` ${key}: ${JSON.stringify(val)}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
console.log();
|
|
121
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Providers commands: providers, providers stats
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { get } from "../api.mjs";
|
|
6
|
+
|
|
7
|
+
function truncate(str, len) {
|
|
8
|
+
if (!str) return "";
|
|
9
|
+
return str.length > len ? str.slice(0, len - 1) + "\u2026" : str;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function listProviders() {
|
|
13
|
+
const raw = await get("/api/providers");
|
|
14
|
+
const data = Array.isArray(raw) ? raw : raw?.providers;
|
|
15
|
+
if (!data || !Array.isArray(data)) {
|
|
16
|
+
console.log("Could not fetch providers.");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (data.length === 0) {
|
|
20
|
+
console.log("No providers found.");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log();
|
|
25
|
+
console.log(`\x1b[1m PROVIDERS\x1b[0m (${data.length})`);
|
|
26
|
+
console.log(` ${"─".repeat(50)}`);
|
|
27
|
+
for (const p of data) {
|
|
28
|
+
const name = truncate(p.name || p.id || "?", 30);
|
|
29
|
+
const type = (p.type || p.category || "").padEnd(15);
|
|
30
|
+
console.log(` ${type} ${name}`);
|
|
31
|
+
}
|
|
32
|
+
console.log();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function showProviderStats() {
|
|
36
|
+
const data = await get("/api/providers/stats");
|
|
37
|
+
if (!data) {
|
|
38
|
+
console.log("Could not fetch provider stats.");
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log();
|
|
43
|
+
console.log(`\x1b[1m PROVIDER STATS\x1b[0m`);
|
|
44
|
+
console.log(` ${"─".repeat(50)}`);
|
|
45
|
+
if (Array.isArray(data)) {
|
|
46
|
+
for (const p of data) {
|
|
47
|
+
const name = (p.name || p.provider || "?").padEnd(20);
|
|
48
|
+
const count = p.count != null ? `${p.count} uses` : "";
|
|
49
|
+
console.log(` ${name} ${count}`);
|
|
50
|
+
}
|
|
51
|
+
} else if (typeof data === "object") {
|
|
52
|
+
for (const [key, val] of Object.entries(data)) {
|
|
53
|
+
console.log(` ${key}: ${JSON.stringify(val)}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
console.log();
|
|
57
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Services commands: services, service, services health, services deps
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { get } from "../api.mjs";
|
|
6
|
+
|
|
7
|
+
function truncate(str, len) {
|
|
8
|
+
if (!str) return "";
|
|
9
|
+
return str.length > len ? str.slice(0, len - 1) + "\u2026" : str;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function listServices() {
|
|
13
|
+
const raw = await get("/api/services");
|
|
14
|
+
const data = Array.isArray(raw) ? raw : raw?.services;
|
|
15
|
+
if (!data || !Array.isArray(data)) {
|
|
16
|
+
console.log("Could not fetch services.");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (data.length === 0) {
|
|
20
|
+
console.log("No services found.");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log();
|
|
25
|
+
console.log(`\x1b[1m SERVICES\x1b[0m (${data.length})`);
|
|
26
|
+
console.log(` ${"─".repeat(60)}`);
|
|
27
|
+
for (const s of data) {
|
|
28
|
+
const name = truncate(s.name || s.id, 30);
|
|
29
|
+
const status = s.status || "?";
|
|
30
|
+
const dot = status === "healthy" || status === "up" ? "\x1b[32m●\x1b[0m" : status === "degraded" ? "\x1b[33m●\x1b[0m" : "\x1b[31m●\x1b[0m";
|
|
31
|
+
console.log(` ${dot} ${name.padEnd(32)} ${status}`);
|
|
32
|
+
}
|
|
33
|
+
console.log();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function showService(args) {
|
|
37
|
+
const id = args[0];
|
|
38
|
+
if (!id) {
|
|
39
|
+
console.log("Usage: cc service <id>");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const data = await get(`/api/services/${encodeURIComponent(id)}`);
|
|
43
|
+
if (!data) {
|
|
44
|
+
console.log(`Service '${id}' not found.`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log();
|
|
49
|
+
console.log(`\x1b[1m ${data.name || data.id}\x1b[0m`);
|
|
50
|
+
console.log(` ${"─".repeat(50)}`);
|
|
51
|
+
if (data.id) console.log(` ID: ${data.id}`);
|
|
52
|
+
if (data.status) console.log(` Status: ${data.status}`);
|
|
53
|
+
if (data.version) console.log(` Version: ${data.version}`);
|
|
54
|
+
if (data.url) console.log(` URL: ${data.url}`);
|
|
55
|
+
if (data.uptime) console.log(` Uptime: ${data.uptime}`);
|
|
56
|
+
console.log();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function showServicesHealth() {
|
|
60
|
+
const data = await get("/api/services/health");
|
|
61
|
+
if (!data) {
|
|
62
|
+
console.log("Could not fetch services health.");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log();
|
|
67
|
+
console.log(`\x1b[1m SERVICES HEALTH\x1b[0m`);
|
|
68
|
+
console.log(` ${"─".repeat(50)}`);
|
|
69
|
+
if (Array.isArray(data)) {
|
|
70
|
+
for (const s of data) {
|
|
71
|
+
const name = truncate(s.name || s.id, 25);
|
|
72
|
+
const status = s.status || s.health || "?";
|
|
73
|
+
const dot = status === "healthy" || status === "up" ? "\x1b[32m●\x1b[0m" : "\x1b[31m●\x1b[0m";
|
|
74
|
+
console.log(` ${dot} ${name.padEnd(27)} ${status}`);
|
|
75
|
+
}
|
|
76
|
+
} else if (typeof data === "object") {
|
|
77
|
+
for (const [key, val] of Object.entries(data)) {
|
|
78
|
+
console.log(` ${key}: ${JSON.stringify(val)}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
console.log();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function showServicesDeps() {
|
|
85
|
+
const data = await get("/api/services/dependencies");
|
|
86
|
+
if (!data) {
|
|
87
|
+
console.log("Could not fetch service dependencies.");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.log();
|
|
92
|
+
console.log(`\x1b[1m SERVICE DEPENDENCIES\x1b[0m`);
|
|
93
|
+
console.log(` ${"─".repeat(50)}`);
|
|
94
|
+
if (Array.isArray(data)) {
|
|
95
|
+
for (const d of data) {
|
|
96
|
+
const from = truncate(d.from || d.service || "?", 20);
|
|
97
|
+
const to = truncate(d.to || d.depends_on || "?", 20);
|
|
98
|
+
console.log(` ${from.padEnd(22)} \x1b[2m→\x1b[0m ${to}`);
|
|
99
|
+
}
|
|
100
|
+
} else if (typeof data === "object") {
|
|
101
|
+
for (const [key, val] of Object.entries(data)) {
|
|
102
|
+
console.log(` ${key}: ${JSON.stringify(val)}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
console.log();
|
|
106
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Traceability commands: trace, trace coverage, trace idea, trace spec
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { get } from "../api.mjs";
|
|
6
|
+
|
|
7
|
+
function truncate(str, len) {
|
|
8
|
+
if (!str) return "";
|
|
9
|
+
return str.length > len ? str.slice(0, len - 1) + "\u2026" : str;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function showTraceability() {
|
|
13
|
+
const data = await get("/api/traceability");
|
|
14
|
+
if (!data) {
|
|
15
|
+
console.log("Could not fetch traceability.");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
console.log();
|
|
20
|
+
console.log(`\x1b[1m TRACEABILITY\x1b[0m`);
|
|
21
|
+
console.log(` ${"─".repeat(50)}`);
|
|
22
|
+
for (const [key, val] of Object.entries(data)) {
|
|
23
|
+
console.log(` ${key}: ${JSON.stringify(val)}`);
|
|
24
|
+
}
|
|
25
|
+
console.log();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function showCoverage() {
|
|
29
|
+
const data = await get("/api/traceability/coverage");
|
|
30
|
+
if (!data) {
|
|
31
|
+
console.log("Could not fetch traceability coverage.");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log();
|
|
36
|
+
console.log(`\x1b[1m TRACEABILITY COVERAGE\x1b[0m`);
|
|
37
|
+
console.log(` ${"─".repeat(50)}`);
|
|
38
|
+
if (data.coverage != null) {
|
|
39
|
+
const pct = typeof data.coverage === "number" ? `${(data.coverage * 100).toFixed(1)}%` : data.coverage;
|
|
40
|
+
console.log(` Coverage: ${pct}`);
|
|
41
|
+
}
|
|
42
|
+
for (const [key, val] of Object.entries(data)) {
|
|
43
|
+
if (key !== "coverage") console.log(` ${key}: ${JSON.stringify(val)}`);
|
|
44
|
+
}
|
|
45
|
+
console.log();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function traceIdea(args) {
|
|
49
|
+
const id = args[0];
|
|
50
|
+
if (!id) {
|
|
51
|
+
console.log("Usage: cc trace idea <id>");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const data = await get(`/api/traceability/idea/${encodeURIComponent(id)}`);
|
|
55
|
+
if (!data) {
|
|
56
|
+
console.log(`No traceability data for idea '${id}'.`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log();
|
|
61
|
+
console.log(`\x1b[1m TRACE: IDEA\x1b[0m ${id}`);
|
|
62
|
+
console.log(` ${"─".repeat(50)}`);
|
|
63
|
+
if (data.specs && Array.isArray(data.specs)) {
|
|
64
|
+
console.log(` Linked specs: ${data.specs.length}`);
|
|
65
|
+
for (const s of data.specs) {
|
|
66
|
+
console.log(` ${truncate(s.id || s.name || JSON.stringify(s), 50)}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (data.contributions && Array.isArray(data.contributions)) {
|
|
70
|
+
console.log(` Contributions: ${data.contributions.length}`);
|
|
71
|
+
}
|
|
72
|
+
// Fallback
|
|
73
|
+
const shown = new Set(["specs", "contributions"]);
|
|
74
|
+
for (const [key, val] of Object.entries(data)) {
|
|
75
|
+
if (!shown.has(key)) console.log(` ${key}: ${JSON.stringify(val)}`);
|
|
76
|
+
}
|
|
77
|
+
console.log();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function traceSpec(args) {
|
|
81
|
+
const id = args[0];
|
|
82
|
+
if (!id) {
|
|
83
|
+
console.log("Usage: cc trace spec <id>");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const data = await get(`/api/traceability/spec/${encodeURIComponent(id)}`);
|
|
87
|
+
if (!data) {
|
|
88
|
+
console.log(`No traceability data for spec '${id}'.`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log();
|
|
93
|
+
console.log(`\x1b[1m TRACE: SPEC\x1b[0m ${id}`);
|
|
94
|
+
console.log(` ${"─".repeat(50)}`);
|
|
95
|
+
if (data.ideas && Array.isArray(data.ideas)) {
|
|
96
|
+
console.log(` Linked ideas: ${data.ideas.length}`);
|
|
97
|
+
for (const i of data.ideas) {
|
|
98
|
+
console.log(` ${truncate(i.id || i.name || JSON.stringify(i), 50)}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Fallback
|
|
102
|
+
const shown = new Set(["ideas"]);
|
|
103
|
+
for (const [key, val] of Object.entries(data)) {
|
|
104
|
+
if (!shown.has(key)) console.log(` ${key}: ${JSON.stringify(val)}`);
|
|
105
|
+
}
|
|
106
|
+
console.log();
|
|
107
|
+
}
|