coherence-cli 0.2.0 → 0.4.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/README.md CHANGED
@@ -1,3 +1,4 @@
1
+ <!-- AUTO-GENERATED from README.template.md. Edit the template, not this file. -->
1
2
  # coherence-cli
2
3
 
3
4
  **Every idea deserves a trail. Every contributor deserves credit.**
@@ -0,0 +1,165 @@
1
+ # coherence-cli
2
+
3
+ **Every idea deserves a trail. Every contributor deserves credit.**
4
+
5
+ `cc` is the command-line interface for [Coherence Network](https://coherencycoin.com) — an open intelligence platform that traces every idea from inception to payout, with fair attribution, coherence scoring, and federated trust.
6
+
7
+ ```
8
+ npm i -g coherence-cli
9
+ cc status
10
+ ```
11
+
12
+ That's it. You're connected to the live network. No account, no signup, no API key needed for reading.
13
+
14
+ ---
15
+
16
+ ## Why this exists
17
+
18
+ Most ideas die in the gap between "great thought" and "shipped thing." The people who research, prototype, review, document, and maintain rarely see proportional credit.
19
+
20
+ Coherence Network changes that. It tracks the full lifecycle:
21
+
22
+ <!-- include: docs/shared/lifecycle-diagram.md -->
23
+
24
+ `cc` gives you direct access to all of it from your terminal.
25
+
26
+ ---
27
+
28
+ ## Quick start
29
+
30
+ ### See what's happening
31
+
32
+ ```bash
33
+ cc ideas # Browse the portfolio ranked by ROI
34
+ cc resonance # What's alive right now
35
+ cc status # Network health, node count, your identity
36
+ ```
37
+
38
+ ### Go deeper
39
+
40
+ ```bash
41
+ cc idea <id> # Full scores, open questions, value gaps
42
+ cc specs # Feature specs with ROI metrics
43
+ cc spec <id> # Implementation summary, pseudocode, cost
44
+ ```
45
+
46
+ ### Contribute
47
+
48
+ ```bash
49
+ cc share # Submit a new idea (interactive)
50
+ cc stake <id> 10 # Stake 10 CC on an idea you believe in
51
+ cc fork <id> # Fork an idea and take it a new direction
52
+ cc contribute # Record any contribution (code, docs, review, design, community)
53
+ ```
54
+
55
+ ### Identity — bring your own
56
+
57
+ Link any identity you already have. No new accounts. 37 providers across 6 categories.
58
+
59
+ ```bash
60
+ cc identity setup # Guided onboarding (first time)
61
+ cc identity link github alice-dev # Link your GitHub
62
+ cc identity link ethereum 0xabc... # Link your wallet
63
+ cc identity link discord user#1234 # Link Discord
64
+ cc identity # See all your linked accounts
65
+ cc identity lookup github alice-dev # Find anyone by their handle
66
+ ```
67
+
68
+ **Supported providers:**
69
+
70
+ <!-- include: docs/shared/identity-providers.md -->
71
+
72
+ You don't need to register anywhere. Just link a provider and start contributing — your work is attributed to your identity across the entire network.
73
+
74
+ ---
75
+
76
+ ## How coherence scoring works
77
+
78
+ <!-- include: docs/shared/coherence-scoring.md -->
79
+
80
+ ---
81
+
82
+ ## The five pillars
83
+
84
+ | Pillar | In practice |
85
+ |--------|-------------|
86
+ | **Traceability** | `cc idea <id>` traces from spark to payout. Nothing is lost. |
87
+ | **Trust** | Coherence scores replace subjective judgement with measurable quality. |
88
+ | **Freedom** | Fork any idea. Run your own node. Vote on governance. No gatekeepers. |
89
+ | **Uniqueness** | Every idea, spec, and contribution is uniquely identified and ranked. |
90
+ | **Collaboration** | Multi-contributor attribution with coherence-weighted payouts. Fair by design. |
91
+
92
+ ---
93
+
94
+ ## The Coherence Network ecosystem
95
+
96
+ Every part of the network links to every other. Jump in wherever makes sense for you.
97
+
98
+ | Surface | What it is | Link |
99
+ |---------|-----------|------|
100
+ | **Web** | The main site — browse ideas, specs, and contributors visually | [coherencycoin.com](https://coherencycoin.com) |
101
+ | **API** | 100+ endpoints, full OpenAPI docs, the engine behind everything | [api.coherencycoin.com/docs](https://api.coherencycoin.com/docs) |
102
+ | **CLI** | This package — terminal-first access to the full network | [npm: coherence-cli](https://www.npmjs.com/package/coherence-cli) |
103
+ | **MCP Server** | 20 typed tools for AI agents (Claude, Cursor, Windsurf, etc.) | [npm: coherence-mcp-server](https://www.npmjs.com/package/coherence-mcp-server) |
104
+ | **OpenClaw Skill** | Auto-triggers in any OpenClaw instance when you mention ideas, specs, or coherence | [ClawHub: coherence-network](https://clawhub.com/skills/coherence-network) |
105
+ | **GitHub** | Source code, specs, issues, and contribution tracking | [github.com/seeker71/Coherence-Network](https://github.com/seeker71/Coherence-Network) |
106
+
107
+ ---
108
+
109
+ ## Configuration
110
+
111
+ By default, `cc` talks to the public API at `https://api.coherencycoin.com`. Override with environment variables:
112
+
113
+ ```bash
114
+ # Point to a local node
115
+ export COHERENCE_API_URL=http://localhost:8000
116
+
117
+ # Enable write operations
118
+ export COHERENCE_API_KEY=your-key
119
+ ```
120
+
121
+ Config is stored in `~/.coherence-network/config.json`.
122
+
123
+ ---
124
+
125
+ ## All commands
126
+
127
+ ```
128
+ cc help Show all commands
129
+ cc ideas [limit] Browse ideas by ROI
130
+ cc idea <id> View idea detail with scores
131
+ cc specs [limit] List feature specs
132
+ cc spec <id> View spec detail
133
+ cc share Submit a new idea
134
+ cc stake <id> <cc> Stake CC on an idea
135
+ cc fork <id> Fork an idea
136
+ cc contribute Record any contribution
137
+ cc resonance What's alive right now
138
+ cc status Network health + node info
139
+ cc identity Show linked accounts
140
+ cc identity setup Guided identity onboarding
141
+ cc identity link <provider> <id> Link a provider identity
142
+ cc identity unlink <provider> Unlink a provider
143
+ cc identity lookup <provider> <id> Find contributor by identity
144
+ ```
145
+
146
+ ---
147
+
148
+ ## Get involved
149
+
150
+ Coherence Network is open source. Every contribution is tracked and attributed — yours will be too.
151
+
152
+ The simplest way to start:
153
+
154
+ ```bash
155
+ cc ideas # find something interesting
156
+ cc contribute # record what you did
157
+ ```
158
+
159
+ Or explore any part of the ecosystem from the table above. Every surface leads to every other.
160
+
161
+ ---
162
+
163
+ ## License
164
+
165
+ MIT
package/bin/cc.mjs CHANGED
@@ -7,29 +7,39 @@
7
7
  * Zero dependencies. Node 18+ required.
8
8
  */
9
9
 
10
- import { listIdeas, showIdea, shareIdea, stakeOnIdea, forkIdea } from "../lib/commands/ideas.mjs";
10
+ import { listIdeas, showIdea, shareIdea, stakeOnIdea, forkIdea, createIdea } from "../lib/commands/ideas.mjs";
11
11
  import { listSpecs, showSpec } from "../lib/commands/specs.mjs";
12
12
  import { contribute } from "../lib/commands/contribute.mjs";
13
13
  import { showStatus, showResonance } from "../lib/commands/status.mjs";
14
- import { showIdentity, linkIdentity, unlinkIdentity, lookupIdentity, setupIdentity } from "../lib/commands/identity.mjs";
14
+ import { showIdentity, linkIdentity, unlinkIdentity, lookupIdentity, setupIdentity, setIdentity } from "../lib/commands/identity.mjs";
15
+ import { listNodes, sendMessage, readMessages } from "../lib/commands/nodes.mjs";
15
16
 
16
17
  const [command, ...args] = process.argv.slice(2);
17
18
 
18
19
  const COMMANDS = {
19
20
  ideas: () => listIdeas(args),
20
- idea: () => showIdea(args),
21
+ idea: () => handleIdea(args),
21
22
  share: () => shareIdea(),
22
23
  stake: () => stakeOnIdea(args),
23
24
  fork: () => forkIdea(args),
24
25
  specs: () => listSpecs(args),
25
26
  spec: () => showSpec(args),
26
- contribute: () => contribute(),
27
+ contribute: () => contribute(args),
27
28
  status: () => showStatus(),
28
29
  resonance: () => showResonance(),
29
30
  identity: () => handleIdentity(args),
31
+ nodes: () => listNodes(),
32
+ msg: () => sendMessage(args),
33
+ messages: () => readMessages(args),
34
+ inbox: () => readMessages(args),
30
35
  help: () => showHelp(),
31
36
  };
32
37
 
38
+ async function handleIdea(args) {
39
+ if (args[0] === "create") return createIdea(args.slice(1));
40
+ return showIdea(args);
41
+ }
42
+
33
43
  async function handleIdentity(args) {
34
44
  const sub = args[0];
35
45
  const subArgs = args.slice(1);
@@ -38,6 +48,7 @@ async function handleIdentity(args) {
38
48
  case "unlink": return unlinkIdentity(subArgs);
39
49
  case "lookup": return lookupIdentity(subArgs);
40
50
  case "setup": return setupIdentity();
51
+ case "set": return setIdentity(subArgs);
41
52
  default: return showIdentity();
42
53
  }
43
54
  }
@@ -51,24 +62,32 @@ function showHelp() {
51
62
  \x1b[1mExplore:\x1b[0m
52
63
  ideas [limit] Browse ideas by ROI
53
64
  idea <id> View idea detail
65
+ idea create <id> <name> [--desc "..." --value N --cost N --parent <id>]
54
66
  specs [limit] List feature specs
55
67
  spec <id> View spec detail
56
68
  resonance What's alive right now
57
69
  status Network health + node info
58
70
 
59
71
  \x1b[1mContribute:\x1b[0m
60
- share Submit a new idea
72
+ share Submit a new idea (interactive)
73
+ contribute Record contribution (interactive)
74
+ contribute --type code --cc 5 --idea <id> --desc "what I did"
61
75
  stake <id> <cc> Stake CC on an idea
62
76
  fork <id> Fork an idea
63
- contribute Record any contribution
64
77
 
65
78
  \x1b[1mIdentity:\x1b[0m
66
79
  identity Show your linked accounts
67
80
  identity setup Guided onboarding
81
+ identity set <id> Set identity non-interactively
68
82
  identity link <p> <id> Link a provider (github, discord, ethereum, ...)
69
83
  identity unlink <p> Unlink a provider
70
84
  identity lookup <p> <id> Find contributor by identity
71
85
 
86
+ \x1b[1mFederation:\x1b[0m
87
+ nodes List federation nodes
88
+ msg <node|broadcast> <text> Send message to a node
89
+ inbox Read your messages
90
+
72
91
  \x1b[1mProviders:\x1b[0m
73
92
  github, x, discord, telegram, mastodon, bluesky, linkedin, reddit,
74
93
  youtube, twitch, instagram, tiktok, gitlab, bitbucket, npm, crates,
@@ -1,14 +1,59 @@
1
1
  /**
2
2
  * Contribute command — record any contribution.
3
+ *
4
+ * Interactive: cc contribute
5
+ * Non-interactive (for agents):
6
+ * cc contribute --type code --cc 5 --idea <id> --desc "what I did"
3
7
  */
4
8
 
5
9
  import { post } from "../api.mjs";
6
10
  import { ensureIdentity } from "../identity.mjs";
7
- import { createInterface } from "node:readline/promises";
8
- import { stdin, stdout } from "node:process";
11
+ import { getContributorId } from "../config.mjs";
9
12
 
10
- export async function contribute() {
13
+ function parseFlags(args) {
14
+ const flags = {};
15
+ for (let i = 0; i < args.length; i++) {
16
+ if (args[i] === "--type" && args[i + 1]) flags.type = args[++i];
17
+ else if (args[i] === "--cc" && args[i + 1]) flags.cc = parseFloat(args[++i]);
18
+ else if (args[i] === "--idea" && args[i + 1]) flags.idea = args[++i];
19
+ else if (args[i] === "--desc" && args[i + 1]) flags.desc = args[++i];
20
+ }
21
+ return flags;
22
+ }
23
+
24
+ export async function contribute(args = []) {
25
+ const flags = parseFlags(args);
26
+ const hasFlags = flags.type || flags.cc || flags.idea || flags.desc;
27
+
28
+ if (hasFlags) {
29
+ // Non-interactive mode (for agents and scripts)
30
+ const contributor = getContributorId() || process.env.COHERENCE_CONTRIBUTOR || "anonymous";
31
+ const type = flags.type || "other";
32
+ const amount = flags.cc || 1.0;
33
+ const ideaId = flags.idea || undefined;
34
+ const description = flags.desc || "";
35
+
36
+ const result = await post("/api/contributions/record", {
37
+ contributor_id: contributor,
38
+ type,
39
+ amount_cc: amount,
40
+ idea_id: ideaId,
41
+ metadata: { description },
42
+ });
43
+
44
+ if (result) {
45
+ console.log(`\x1b[32m✓\x1b[0m ${type} ${amount} CC${ideaId ? ` → ${ideaId}` : ""}${description ? ` (${description})` : ""}`);
46
+ } else {
47
+ console.log("Failed to record contribution.");
48
+ process.exit(1);
49
+ }
50
+ return;
51
+ }
52
+
53
+ // Interactive mode
11
54
  const contributor = await ensureIdentity();
55
+ const { createInterface } = await import("node:readline/promises");
56
+ const { stdin, stdout } = await import("node:process");
12
57
  const rl = createInterface({ input: stdin, output: stdout });
13
58
 
14
59
  console.log();
@@ -136,3 +136,44 @@ export async function forkIdea(args) {
136
136
  console.log("Fork failed.");
137
137
  }
138
138
  }
139
+
140
+ /**
141
+ * Non-interactive idea creation for agents and scripts.
142
+ *
143
+ * Usage: cc idea create <id> <name> [--desc "..."] [--value N] [--cost N] [--parent <id>]
144
+ */
145
+ export async function createIdea(args) {
146
+ if (args.length < 2) {
147
+ console.log("Usage: cc idea create <id> <name> [--desc \"...\"] [--value N] [--cost N] [--parent <id>]");
148
+ return;
149
+ }
150
+
151
+ const id = args[0];
152
+ const name = args[1];
153
+ const flags = {};
154
+ for (let i = 2; i < args.length; i++) {
155
+ if (args[i] === "--desc" && args[i + 1]) flags.desc = args[++i];
156
+ else if (args[i] === "--value" && args[i + 1]) flags.value = parseFloat(args[++i]);
157
+ else if (args[i] === "--cost" && args[i + 1]) flags.cost = parseFloat(args[++i]);
158
+ else if (args[i] === "--parent" && args[i + 1]) flags.parent = args[++i];
159
+ else if (args[i] === "--confidence" && args[i + 1]) flags.confidence = parseFloat(args[++i]);
160
+ }
161
+
162
+ const body = {
163
+ id,
164
+ name,
165
+ description: flags.desc || name,
166
+ potential_value: flags.value || 50,
167
+ estimated_cost: flags.cost || 5,
168
+ confidence: flags.confidence || 0.5,
169
+ };
170
+ if (flags.parent) body.parent_idea_id = flags.parent;
171
+
172
+ const result = await post("/api/ideas", body);
173
+ if (result) {
174
+ console.log(`\x1b[32m✓\x1b[0m Idea created: ${result.id || id}`);
175
+ } else {
176
+ console.log("Failed to create idea.");
177
+ process.exit(1);
178
+ }
179
+ }
@@ -86,6 +86,18 @@ export async function lookupIdentity(args) {
86
86
  }
87
87
  }
88
88
 
89
+ export async function setIdentity(args) {
90
+ const id = args[0];
91
+ if (!id) {
92
+ console.log("Usage: cc identity set <contributor_id>");
93
+ console.log("Sets the contributor identity non-interactively (for agents and scripts).");
94
+ return;
95
+ }
96
+ const { saveConfig } = await import("../config.mjs");
97
+ saveConfig({ contributor_id: id });
98
+ console.log(`\x1b[32m✓\x1b[0m Identity set to: ${id}`);
99
+ }
100
+
89
101
  export async function setupIdentity() {
90
102
  // Force re-run onboarding
91
103
  await ensureIdentity();
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Federation node commands: nodes, msg, broadcast
3
+ */
4
+
5
+ import { get, post } from "../api.mjs";
6
+ import { hostname } from "node:os";
7
+
8
+ export async function listNodes() {
9
+ const nodes = await get("/api/federation/nodes");
10
+ if (!nodes || !Array.isArray(nodes)) {
11
+ console.log("Could not fetch federation nodes.");
12
+ return;
13
+ }
14
+
15
+ console.log();
16
+ console.log("\x1b[1m FEDERATION NODES\x1b[0m");
17
+ console.log(` ${"─".repeat(50)}`);
18
+
19
+ const now = Date.now();
20
+ for (const node of nodes) {
21
+ const lastSeen = node.last_seen_at ? new Date(node.last_seen_at) : null;
22
+ const ageMs = lastSeen ? now - lastSeen.getTime() : Infinity;
23
+ const ageMin = Math.floor(ageMs / 60000);
24
+
25
+ // Status dot
26
+ let dot = "\x1b[31m●\x1b[0m"; // red
27
+ if (ageMin < 5) dot = "\x1b[32m●\x1b[0m"; // green
28
+ else if (ageMin < 60) dot = "\x1b[33m●\x1b[0m"; // yellow
29
+
30
+ // OS icon
31
+ const os = node.os_type || "?";
32
+ const icon = os === "macos" ? "🍎" : os === "windows" ? "🪟" : os === "linux" ? "🐧" : "🖥️";
33
+
34
+ // Providers
35
+ let providers = [];
36
+ try {
37
+ providers = typeof node.providers_json === "string"
38
+ ? JSON.parse(node.providers_json)
39
+ : (node.providers || []);
40
+ } catch { providers = []; }
41
+
42
+ const ago = ageMin < 1 ? "now" : ageMin < 60 ? `${ageMin}m ago` : `${Math.floor(ageMin / 60)}h ago`;
43
+
44
+ console.log(` ${dot} ${icon} \x1b[1m${node.hostname || "?"}\x1b[0m ${ago}`);
45
+ console.log(` ${providers.join(", ")}`);
46
+ console.log(` id: ${node.node_id}`);
47
+ console.log();
48
+ }
49
+ }
50
+
51
+ export async function sendMessage(args) {
52
+ const [targetOrBroadcast, ...textParts] = args;
53
+ const text = textParts.join(" ");
54
+
55
+ if (!targetOrBroadcast || !text) {
56
+ console.log("Usage: cc msg <node_id|broadcast> <message text>");
57
+ console.log(" cc msg broadcast Hello all nodes!");
58
+ console.log(" cc msg e66ff3d35dd4ccb1 Hello Mac node!");
59
+ return;
60
+ }
61
+
62
+ // Determine our node ID from hostname
63
+ const myHost = hostname();
64
+ const nodes = await get("/api/federation/nodes");
65
+ let myNodeId = null;
66
+ if (Array.isArray(nodes)) {
67
+ const mine = nodes.find(n => n.hostname === myHost);
68
+ if (mine) myNodeId = mine.node_id;
69
+ }
70
+ if (!myNodeId) {
71
+ // Fallback: use first 16 chars of hostname hash
72
+ myNodeId = myHost;
73
+ }
74
+
75
+ if (targetOrBroadcast === "broadcast" || targetOrBroadcast === "all") {
76
+ const result = await post("/api/federation/broadcast", {
77
+ from_node: myNodeId,
78
+ to_node: null,
79
+ type: "text",
80
+ text,
81
+ payload: {},
82
+ });
83
+ if (result) {
84
+ console.log(`\x1b[32m✓\x1b[0m Broadcast sent: ${text.slice(0, 60)}`);
85
+ } else {
86
+ console.log("\x1b[31m✗\x1b[0m Failed to broadcast");
87
+ }
88
+ } else {
89
+ const result = await post(`/api/federation/nodes/${myNodeId}/messages`, {
90
+ from_node: myNodeId,
91
+ to_node: targetOrBroadcast,
92
+ type: "text",
93
+ text,
94
+ payload: {},
95
+ });
96
+ if (result) {
97
+ console.log(`\x1b[32m✓\x1b[0m Message sent to ${targetOrBroadcast.slice(0, 12)}: ${text.slice(0, 60)}`);
98
+ } else {
99
+ console.log("\x1b[31m✗\x1b[0m Failed to send message");
100
+ }
101
+ }
102
+ }
103
+
104
+ export async function readMessages(args) {
105
+ const myHost = hostname();
106
+ const nodes = await get("/api/federation/nodes");
107
+ let myNodeId = null;
108
+ if (Array.isArray(nodes)) {
109
+ const mine = nodes.find(n => n.hostname === myHost);
110
+ if (mine) myNodeId = mine.node_id;
111
+ }
112
+ if (!myNodeId) {
113
+ console.log("Could not determine your node ID. Register first.");
114
+ return;
115
+ }
116
+
117
+ const data = await get(`/api/federation/nodes/${myNodeId}/messages?unread_only=false&limit=20`);
118
+ if (!data || !data.messages) {
119
+ console.log("No messages.");
120
+ return;
121
+ }
122
+
123
+ console.log();
124
+ console.log("\x1b[1m MESSAGES\x1b[0m");
125
+ console.log(` ${"─".repeat(50)}`);
126
+
127
+ if (data.messages.length === 0) {
128
+ console.log(" No messages yet.");
129
+ }
130
+
131
+ for (const msg of data.messages) {
132
+ const ts = msg.timestamp ? new Date(msg.timestamp).toLocaleString() : "?";
133
+ const from = msg.from_node ? msg.from_node.slice(0, 12) : "?";
134
+ const type = msg.type || "text";
135
+ const unread = !msg.read_by?.includes(myNodeId) ? " \x1b[33m(new)\x1b[0m" : "";
136
+
137
+ console.log(` \x1b[2m${ts}\x1b[0m [${type}] from ${from}${unread}`);
138
+ console.log(` ${msg.text || "(no text)"}`);
139
+ console.log();
140
+ }
141
+ }
@@ -7,31 +7,118 @@ import { getContributorId, getHubUrl } from "../config.mjs";
7
7
  import { hostname } from "node:os";
8
8
 
9
9
  export async function showStatus() {
10
- const health = await get("/api/health");
11
- const ideas = await get("/api/ideas/count");
12
- const nodes = await get("/api/federation/nodes");
10
+ // Fetch everything in parallel
11
+ const [health, ideas, nodes, pendingData, runningData, completedData, coherence, ledger, messages] =
12
+ await Promise.all([
13
+ get("/api/health"),
14
+ get("/api/ideas/count"),
15
+ get("/api/federation/nodes"),
16
+ get("/api/agent/tasks", { status: "pending", limit: 100 }),
17
+ get("/api/agent/tasks", { status: "running", limit: 100 }),
18
+ get("/api/agent/tasks", { status: "completed", limit: 5 }),
19
+ get("/api/coherence/score"),
20
+ getContributorId() ? get(`/api/contributions/ledger/${encodeURIComponent(getContributorId())}`) : null,
21
+ getContributorId() ? get(`/api/federation/nodes/${encodeURIComponent(getContributorId())}/messages`, { unread_only: true, limit: 10 }) : null,
22
+ ]);
23
+
24
+ const taskList = (d) => {
25
+ if (!d) return [];
26
+ if (Array.isArray(d)) return d;
27
+ return d.tasks || [];
28
+ };
29
+
30
+ const pending = taskList(pendingData);
31
+ const running = taskList(runningData);
32
+ const completed = taskList(completedData);
13
33
 
14
34
  console.log();
15
35
  console.log("\x1b[1m COHERENCE NETWORK STATUS\x1b[0m");
16
- console.log(` ${"─".repeat(40)}`);
36
+ console.log(` ${"─".repeat(50)}`);
17
37
 
38
+ // API health
18
39
  if (health) {
19
- console.log(` API: \x1b[32m${health.status}\x1b[0m (${health.version || "?"})`);
20
- console.log(` Uptime: ${health.uptime_human || "?"}`);
40
+ const schemaIcon = health.schema_ok === false ? "\x1b[31m✗\x1b[0m" : "\x1b[32m✓\x1b[0m";
41
+ console.log(` API: \x1b[32m${health.status}\x1b[0m (${health.version || "?"}) ${schemaIcon} schema`);
42
+ console.log(` Uptime: ${health.uptime_human || "?"}`);
21
43
  } else {
22
- console.log(` API: \x1b[31moffline\x1b[0m`);
44
+ console.log(` API: \x1b[31moffline\x1b[0m`);
45
+ }
46
+
47
+ // Coherence score
48
+ if (coherence && coherence.score != null) {
49
+ const score = coherence.score.toFixed(2);
50
+ const color = coherence.score >= 0.7 ? "\x1b[32m" : coherence.score >= 0.4 ? "\x1b[33m" : "\x1b[31m";
51
+ console.log(` Coherence: ${color}${score}\x1b[0m (${coherence.signals_with_data || 0}/${coherence.total_signals || 0} signals)`);
23
52
  }
24
53
 
25
- console.log(` Hub: ${getHubUrl()}`);
26
- console.log(` Node: ${hostname()}`);
27
- console.log(` Identity: ${getContributorId() || "(not set — run: cc identity setup)"}`);
54
+ console.log(` Hub: ${getHubUrl()}`);
55
+ console.log(` Node: ${hostname()}`);
56
+ console.log(` Identity: ${getContributorId() || "\x1b[33m(not set — run: cc identity set <id>)\x1b[0m"}`);
28
57
 
29
- if (ideas?.count != null) {
30
- console.log(` Ideas: ${ideas.count}`);
58
+ // Ideas
59
+ if (ideas) {
60
+ const byStatus = ideas.by_status || {};
61
+ console.log(` Ideas: ${ideas.total || 0} (${byStatus.validated || 0} validated, ${byStatus.none || 0} open)`);
31
62
  }
32
63
 
64
+ // Nodes
33
65
  if (Array.isArray(nodes) && nodes.length > 0) {
34
- console.log(` Fed. Nodes: ${nodes.length}`);
66
+ const now = Date.now();
67
+ const alive = nodes.filter((n) => {
68
+ const last = n.last_heartbeat || n.registered_at || "";
69
+ if (!last) return false;
70
+ return now - new Date(last).getTime() < 600_000; // 10 min
71
+ });
72
+ console.log(` Nodes: ${nodes.length} registered (${alive.length} live)`);
73
+ }
74
+
75
+ // Pipeline
76
+ console.log();
77
+ console.log("\x1b[1m PIPELINE\x1b[0m");
78
+ console.log(` ${"─".repeat(50)}`);
79
+ console.log(` Pending: ${pending.length}`);
80
+ console.log(` Running: ${running.length}`);
81
+
82
+ if (running.length > 0) {
83
+ for (const t of running.slice(0, 3)) {
84
+ const ctx = t.context || {};
85
+ const name = ctx.idea_name || t.direction?.slice(0, 40) || t.id?.slice(0, 16) || "?";
86
+ const age = t.created_at ? Math.round((Date.now() - new Date(t.created_at).getTime()) / 60000) : 0;
87
+ console.log(` \x1b[33m▸\x1b[0m ${t.task_type || "?"} ${age}m ${name}`);
88
+ }
89
+ if (running.length > 3) console.log(` ... and ${running.length - 3} more`);
90
+ }
91
+
92
+ // Recent completions
93
+ if (completed.length > 0) {
94
+ console.log(` Recent: last ${completed.length} completed`);
95
+ for (const t of completed.slice(0, 3)) {
96
+ const ctx = t.context || {};
97
+ const name = ctx.idea_name || t.id?.slice(0, 16) || "?";
98
+ console.log(` \x1b[32m✓\x1b[0m ${t.task_type || "?"} ${name}`);
99
+ }
100
+ }
101
+
102
+ // My ledger
103
+ if (ledger && ledger.balance) {
104
+ console.log();
105
+ console.log("\x1b[1m MY LEDGER\x1b[0m");
106
+ console.log(` ${"─".repeat(50)}`);
107
+ console.log(` Total: ${ledger.balance.grand_total || 0} CC`);
108
+ const types = ledger.balance.totals_by_type || {};
109
+ const sorted = Object.entries(types).sort((a, b) => b[1] - a[1]);
110
+ for (const [type, amount] of sorted) {
111
+ console.log(` ${type.padEnd(14)} ${amount} CC`);
112
+ }
113
+ }
114
+
115
+ // Messages
116
+ if (messages && messages.count > 0) {
117
+ console.log();
118
+ console.log(`\x1b[33m 📬 ${messages.count} unread message(s)\x1b[0m`);
119
+ for (const m of (messages.messages || []).slice(0, 3)) {
120
+ console.log(` from ${(m.from_node || "?").slice(0, 12)}: ${(m.text || "").slice(0, 60)}`);
121
+ }
35
122
  }
36
123
 
37
124
  console.log();
package/package.json CHANGED
@@ -1,10 +1,12 @@
1
1
  {
2
2
  "name": "coherence-cli",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Coherence Network CLI — trace ideas from inception to payout, with fair attribution, coherence scoring, and 37 identity providers. No signup needed.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "cc": "bin/cc.mjs"
7
+ "cc": "bin/cc.mjs",
8
+ "coh": "bin/cc.mjs",
9
+ "coherence": "bin/cc.mjs"
8
10
  },
9
11
  "engines": {
10
12
  "node": ">=18.0.0"