coherence-cli 0.4.1 → 0.6.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/README.md CHANGED
@@ -59,6 +59,39 @@ cc fork <id> # Fork an idea and take it a new direction
59
59
  cc contribute # Record any contribution (code, docs, review, design, community)
60
60
  ```
61
61
 
62
+ ### Tasks — agent-to-agent work protocol
63
+
64
+ AI agents and human contributors use the same task queue. Pick up work, execute it, report back.
65
+
66
+ ```bash
67
+ cc tasks # See what's pending
68
+ cc tasks running # See what's in progress
69
+ cc task next # Claim the highest-priority pending task
70
+ cc task <id> # View task detail (direction, idea link, context)
71
+ cc task claim <id> # Claim a specific task
72
+ cc task report <id> completed "All tests pass" # Report success
73
+ cc task report <id> failed "Missing dependency" # Report failure
74
+ cc task seed <idea-id> spec # Create a spec task from an idea
75
+ ```
76
+
77
+ When piped (non-TTY), `cc task next` outputs raw JSON — so an AI agent can parse it programmatically:
78
+
79
+ ```bash
80
+ TASK=$(cc task next 2>/dev/null | tail -1)
81
+ # Agent processes the task...
82
+ cc task report $(echo $TASK | jq -r .id) completed "Done"
83
+ ```
84
+
85
+ ### Federation — multi-node coordination
86
+
87
+ ```bash
88
+ cc nodes # See all federation nodes (status, providers, last seen)
89
+ cc msg broadcast "Update ready" # Broadcast to all nodes
90
+ cc msg <node_id> "Run tests" # Message a specific node
91
+ cc cmd <node> diagnose # Send a structured command
92
+ cc inbox # Read your messages
93
+ ```
94
+
62
95
  ### Identity — bring your own
63
96
 
64
97
  Link any identity you already have. No new accounts. 37 providers across 6 categories.
@@ -146,21 +179,42 @@ Config is stored in `~/.coherence-network/config.json`.
146
179
 
147
180
  ```
148
181
  cc help Show all commands
182
+
183
+ # Explore
149
184
  cc ideas [limit] Browse ideas by ROI
150
185
  cc idea <id> View idea detail with scores
186
+ cc idea create <id> <name> Create a new idea
151
187
  cc specs [limit] List feature specs
152
188
  cc spec <id> View spec detail
153
- cc share Submit a new idea
189
+ cc resonance What's alive right now
190
+ cc status Network health + node info
191
+
192
+ # Contribute
193
+ cc share Submit a new idea (interactive)
154
194
  cc stake <id> <cc> Stake CC on an idea
155
195
  cc fork <id> Fork an idea
156
196
  cc contribute Record any contribution
157
- cc resonance What's alive right now
158
- cc status Network health + node info
197
+
198
+ # Tasks (agent-to-agent)
199
+ cc tasks [status] [limit] List tasks (pending|running|completed)
200
+ cc task <id> View task detail
201
+ cc task next Claim next pending task
202
+ cc task claim <id> Claim a specific task
203
+ cc task report <id> <status> [output] Report result
204
+ cc task seed <idea> [type] Create task from idea
205
+
206
+ # Identity
159
207
  cc identity Show linked accounts
160
208
  cc identity setup Guided identity onboarding
161
209
  cc identity link <provider> <id> Link a provider identity
162
210
  cc identity unlink <provider> Unlink a provider
163
211
  cc identity lookup <provider> <id> Find contributor by identity
212
+
213
+ # Federation
214
+ cc nodes List federation nodes
215
+ cc msg <node|broadcast> <text> Send message to a node
216
+ cc cmd <node> <command> Send structured command
217
+ cc inbox Read your messages
164
218
  ```
165
219
 
166
220
  ---
@@ -52,6 +52,39 @@ cc fork <id> # Fork an idea and take it a new direction
52
52
  cc contribute # Record any contribution (code, docs, review, design, community)
53
53
  ```
54
54
 
55
+ ### Tasks — agent-to-agent work protocol
56
+
57
+ AI agents and human contributors use the same task queue. Pick up work, execute it, report back.
58
+
59
+ ```bash
60
+ cc tasks # See what's pending
61
+ cc tasks running # See what's in progress
62
+ cc task next # Claim the highest-priority pending task
63
+ cc task <id> # View task detail (direction, idea link, context)
64
+ cc task claim <id> # Claim a specific task
65
+ cc task report <id> completed "All tests pass" # Report success
66
+ cc task report <id> failed "Missing dependency" # Report failure
67
+ cc task seed <idea-id> spec # Create a spec task from an idea
68
+ ```
69
+
70
+ When piped (non-TTY), `cc task next` outputs raw JSON — so an AI agent can parse it programmatically:
71
+
72
+ ```bash
73
+ TASK=$(cc task next 2>/dev/null | tail -1)
74
+ # Agent processes the task...
75
+ cc task report $(echo $TASK | jq -r .id) completed "Done"
76
+ ```
77
+
78
+ ### Federation — multi-node coordination
79
+
80
+ ```bash
81
+ cc nodes # See all federation nodes (status, providers, last seen)
82
+ cc msg broadcast "Update ready" # Broadcast to all nodes
83
+ cc msg <node_id> "Run tests" # Message a specific node
84
+ cc cmd <node> diagnose # Send a structured command
85
+ cc inbox # Read your messages
86
+ ```
87
+
55
88
  ### Identity — bring your own
56
89
 
57
90
  Link any identity you already have. No new accounts. 37 providers across 6 categories.
@@ -126,21 +159,42 @@ Config is stored in `~/.coherence-network/config.json`.
126
159
 
127
160
  ```
128
161
  cc help Show all commands
162
+
163
+ # Explore
129
164
  cc ideas [limit] Browse ideas by ROI
130
165
  cc idea <id> View idea detail with scores
166
+ cc idea create <id> <name> Create a new idea
131
167
  cc specs [limit] List feature specs
132
168
  cc spec <id> View spec detail
133
- cc share Submit a new idea
169
+ cc resonance What's alive right now
170
+ cc status Network health + node info
171
+
172
+ # Contribute
173
+ cc share Submit a new idea (interactive)
134
174
  cc stake <id> <cc> Stake CC on an idea
135
175
  cc fork <id> Fork an idea
136
176
  cc contribute Record any contribution
137
- cc resonance What's alive right now
138
- cc status Network health + node info
177
+
178
+ # Tasks (agent-to-agent)
179
+ cc tasks [status] [limit] List tasks (pending|running|completed)
180
+ cc task <id> View task detail
181
+ cc task next Claim next pending task
182
+ cc task claim <id> Claim a specific task
183
+ cc task report <id> <status> [output] Report result
184
+ cc task seed <idea> [type] Create task from idea
185
+
186
+ # Identity
139
187
  cc identity Show linked accounts
140
188
  cc identity setup Guided identity onboarding
141
189
  cc identity link <provider> <id> Link a provider identity
142
190
  cc identity unlink <provider> Unlink a provider
143
191
  cc identity lookup <provider> <id> Find contributor by identity
192
+
193
+ # Federation
194
+ cc nodes List federation nodes
195
+ cc msg <node|broadcast> <text> Send message to a node
196
+ cc cmd <node> <command> Send structured command
197
+ cc inbox Read your messages
144
198
  ```
145
199
 
146
200
  ---
package/bin/cc.mjs CHANGED
@@ -7,12 +7,46 @@
7
7
  * Zero dependencies. Node 18+ required.
8
8
  */
9
9
 
10
+ import { createRequire } from "node:module";
10
11
  import { listIdeas, showIdea, shareIdea, stakeOnIdea, forkIdea, createIdea } from "../lib/commands/ideas.mjs";
11
12
  import { listSpecs, showSpec } from "../lib/commands/specs.mjs";
12
13
  import { contribute } from "../lib/commands/contribute.mjs";
13
14
  import { showStatus, showResonance } from "../lib/commands/status.mjs";
14
15
  import { showIdentity, linkIdentity, unlinkIdentity, lookupIdentity, setupIdentity, setIdentity } from "../lib/commands/identity.mjs";
15
- import { listNodes, sendMessage, readMessages } from "../lib/commands/nodes.mjs";
16
+ import { listNodes, sendMessage, readMessages, sendCommand } from "../lib/commands/nodes.mjs";
17
+ import { listTasks, showTask, claimTask, claimNext, reportTask, seedTask } from "../lib/commands/tasks.mjs";
18
+
19
+ // Version check — non-blocking, runs in background
20
+ const require = createRequire(import.meta.url);
21
+ const pkg = require("../package.json");
22
+ const LOCAL_VERSION = pkg.version;
23
+
24
+ async function checkForUpdate() {
25
+ try {
26
+ const resp = await fetch("https://registry.npmjs.org/coherence-cli/latest", {
27
+ signal: AbortSignal.timeout(3000),
28
+ });
29
+ if (!resp.ok) return;
30
+ const data = await resp.json();
31
+ const latest = data.version;
32
+ if (latest && latest !== LOCAL_VERSION && latest > LOCAL_VERSION) {
33
+ console.log(
34
+ `\n\x1b[33m Update available: ${LOCAL_VERSION} → ${latest} — auto-updating...\x1b[0m`,
35
+ );
36
+ const { execSync } = await import("node:child_process");
37
+ try {
38
+ execSync(`npm i -g coherence-cli@${latest}`, { stdio: "pipe" });
39
+ console.log(`\x1b[32m ✓ Updated to v${latest}\x1b[0m\n`);
40
+ } catch {
41
+ console.log(`\x1b[31m ✗ Auto-update failed. Run: npm i -g coherence-cli@${latest}\x1b[0m\n`);
42
+ }
43
+ }
44
+ } catch {
45
+ // Silent — don't block the CLI for a version check
46
+ }
47
+ }
48
+
49
+ const updateCheck = checkForUpdate();
16
50
 
17
51
  const [command, ...args] = process.argv.slice(2);
18
52
 
@@ -30,8 +64,13 @@ const COMMANDS = {
30
64
  identity: () => handleIdentity(args),
31
65
  nodes: () => listNodes(),
32
66
  msg: () => sendMessage(args),
67
+ cmd: () => sendCommand(args),
33
68
  messages: () => readMessages(args),
34
69
  inbox: () => readMessages(args),
70
+ tasks: () => listTasks(args),
71
+ task: () => handleTask(args),
72
+ update: () => selfUpdate(),
73
+ version: () => console.log(`cc v${LOCAL_VERSION}`),
35
74
  help: () => showHelp(),
36
75
  };
37
76
 
@@ -40,6 +79,17 @@ async function handleIdea(args) {
40
79
  return showIdea(args);
41
80
  }
42
81
 
82
+ async function handleTask(args) {
83
+ const sub = args[0];
84
+ switch (sub) {
85
+ case "next": return claimNext();
86
+ case "claim": return claimTask(args.slice(1));
87
+ case "report": return reportTask(args.slice(1));
88
+ case "seed": return seedTask(args.slice(1));
89
+ default: return showTask(args);
90
+ }
91
+ }
92
+
43
93
  async function handleIdentity(args) {
44
94
  const sub = args[0];
45
95
  const subArgs = args.slice(1);
@@ -53,6 +103,29 @@ async function handleIdentity(args) {
53
103
  }
54
104
  }
55
105
 
106
+ async function selfUpdate() {
107
+ const { execSync } = await import("node:child_process");
108
+ console.log(`Current: v${LOCAL_VERSION}`);
109
+ console.log("Checking npm for latest version...");
110
+ try {
111
+ const resp = await fetch("https://registry.npmjs.org/coherence-cli/latest", {
112
+ signal: AbortSignal.timeout(5000),
113
+ });
114
+ const data = await resp.json();
115
+ const latest = data.version;
116
+ if (latest === LOCAL_VERSION) {
117
+ console.log(`\x1b[32m✓\x1b[0m Already on latest version (${LOCAL_VERSION})`);
118
+ return;
119
+ }
120
+ console.log(`Updating ${LOCAL_VERSION} → ${latest}...`);
121
+ execSync(`npm i -g coherence-cli@${latest}`, { stdio: "inherit" });
122
+ console.log(`\x1b[32m✓\x1b[0m Updated to v${latest}`);
123
+ } catch (e) {
124
+ console.error(`\x1b[31m✗\x1b[0m Update failed: ${e.message}`);
125
+ console.log(" Manual: npm i -g coherence-cli@latest");
126
+ }
127
+ }
128
+
56
129
  function showHelp() {
57
130
  console.log(`
58
131
  \x1b[1mcc\x1b[0m — Coherence Network CLI
@@ -83,11 +156,25 @@ function showHelp() {
83
156
  identity unlink <p> Unlink a provider
84
157
  identity lookup <p> <id> Find contributor by identity
85
158
 
159
+ \x1b[1mTasks (agent-to-agent):\x1b[0m
160
+ tasks [status] [limit] List tasks (pending|running|completed)
161
+ task <id> View task detail
162
+ task next Claim next pending task (for AI agents)
163
+ task claim <id> Claim a specific task
164
+ task report <id> <status> [output] Report result (completed|failed)
165
+ task seed <idea> [type] Create task from idea (spec|test|impl|review)
166
+
86
167
  \x1b[1mFederation:\x1b[0m
87
168
  nodes List federation nodes
88
169
  msg <node|broadcast> <text> Send message to a node
170
+ cmd <node> <command> Remote command (update|status|diagnose|restart|ping)
89
171
  inbox Read your messages
90
172
 
173
+ \x1b[1mSystem:\x1b[0m
174
+ update Self-update to latest npm version
175
+ version Show current version
176
+ help Show this help
177
+
91
178
  \x1b[1mProviders:\x1b[0m
92
179
  github, x, discord, telegram, mastodon, bluesky, linkedin, reddit,
93
180
  youtube, twitch, instagram, tiktok, gitlab, bitbucket, npm, crates,
package/lib/api.mjs CHANGED
@@ -51,6 +51,21 @@ export async function post(path, body) {
51
51
  }
52
52
  }
53
53
 
54
+ export async function patch(path, body) {
55
+ try {
56
+ const res = await fetch(buildUrl(path), {
57
+ method: "PATCH",
58
+ headers: { "Content-Type": "application/json" },
59
+ body: JSON.stringify(body),
60
+ signal: AbortSignal.timeout(TIMEOUT_MS),
61
+ });
62
+ if (!res.ok) return null;
63
+ return await res.json();
64
+ } catch {
65
+ return null;
66
+ }
67
+ }
68
+
54
69
  export async function del(path) {
55
70
  try {
56
71
  const res = await fetch(buildUrl(path), {
@@ -1,10 +1,66 @@
1
1
  /**
2
- * Federation node commands: nodes, msg, broadcast
2
+ * Federation node commands: nodes, msg, cmd, broadcast
3
3
  */
4
4
 
5
5
  import { get, post } from "../api.mjs";
6
6
  import { hostname } from "node:os";
7
7
 
8
+ /**
9
+ * Send a remote command to a node.
10
+ * Usage: cc cmd <node_id_or_name> <command> [...args]
11
+ * Commands: update, status, diagnose, restart, ping
12
+ */
13
+ export async function sendCommand(args) {
14
+ if (args.length < 2) {
15
+ console.log("Usage: cc cmd <node_id_or_name> <command>");
16
+ console.log("Commands: update, status, diagnose, restart, ping");
17
+ return;
18
+ }
19
+
20
+ const [target, command, ...cmdArgs] = args;
21
+
22
+ // Resolve target: could be node_id prefix or hostname
23
+ const nodes = await get("/api/federation/nodes");
24
+ const node = nodes?.find(
25
+ (n) =>
26
+ n.node_id?.startsWith(target) ||
27
+ n.hostname?.toLowerCase().includes(target.toLowerCase()),
28
+ );
29
+ if (!node) {
30
+ console.log(`Node not found: ${target}`);
31
+ console.log("Available nodes:");
32
+ for (const n of nodes || []) {
33
+ console.log(` ${n.node_id?.slice(0, 12)} ${n.hostname}`);
34
+ }
35
+ return;
36
+ }
37
+
38
+ const myNodeId = nodes?.find(
39
+ (n) => n.hostname === hostname(),
40
+ )?.node_id || "unknown";
41
+
42
+ console.log(
43
+ `Sending \x1b[1m${command}\x1b[0m to \x1b[1m${node.hostname}\x1b[0m (${node.node_id?.slice(0, 12)})...`,
44
+ );
45
+
46
+ const result = await post(`/api/federation/nodes/${myNodeId}/messages`, {
47
+ from_node: myNodeId,
48
+ to_node: node.node_id,
49
+ type: "command",
50
+ text: `Remote command: ${command} ${cmdArgs.join(" ")}`.trim(),
51
+ payload: { command, args: cmdArgs },
52
+ });
53
+
54
+ if (result?.id) {
55
+ console.log(`\x1b[32m✓\x1b[0m Command sent (msg ${result.id.slice(0, 12)})`);
56
+ console.log(
57
+ " Node will execute on next poll cycle (~2 min) and reply.",
58
+ );
59
+ } else {
60
+ console.log("\x1b[31m✗\x1b[0m Failed to send command");
61
+ }
62
+ }
63
+
8
64
  export async function listNodes() {
9
65
  const nodes = await get("/api/federation/nodes");
10
66
  if (!nodes || !Array.isArray(nodes)) {
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Task commands: list, claim, report, next
3
+ *
4
+ * This is the agent-to-agent work protocol. An AI agent (Claude, Codex,
5
+ * Cursor, Gemini) can:
6
+ * cc tasks — see what's pending/running
7
+ * cc task next — claim the highest-priority pending task
8
+ * cc task <id> — view task detail
9
+ * cc task claim <id> — claim a specific task
10
+ * cc task report <id> <status> [output] — report result
11
+ * cc task seed <idea> — create a task from an idea
12
+ */
13
+
14
+ import { get, post, patch } from "../api.mjs";
15
+ import { hostname } from "node:os";
16
+ import { createHash } from "node:crypto";
17
+
18
+ function nodeId() {
19
+ return createHash("sha256").update(hostname()).digest("hex").slice(0, 16);
20
+ }
21
+
22
+ export async function listTasks(args) {
23
+ const status = args[0] || "pending";
24
+ const limit = parseInt(args[1]) || 10;
25
+ const data = await get("/api/agent/tasks", { status, limit });
26
+ const tasks = data?.tasks || (Array.isArray(data) ? data : []);
27
+
28
+ console.log();
29
+ console.log(`\x1b[1m TASKS\x1b[0m (${status}, ${tasks.length})`)
30
+ console.log(` ${"─".repeat(60)}`);
31
+
32
+ if (!tasks.length) {
33
+ console.log(` No ${status} tasks.`);
34
+ return;
35
+ }
36
+
37
+ for (const t of tasks) {
38
+ const type = (t.type || t.phase || "?").padEnd(7);
39
+ const age = t.created_at ? timeSince(t.created_at) : "?";
40
+ const dir = (t.direction || t.description || "").slice(0, 50);
41
+ const ctx = t.context || {};
42
+ const ideaId = ctx.idea_id || "";
43
+ const statusMark = t.status === "running" ? "\x1b[33m▸\x1b[0m" :
44
+ t.status === "completed" ? "\x1b[32m✓\x1b[0m" :
45
+ t.status === "failed" ? "\x1b[31m✗\x1b[0m" : "\x1b[2m○\x1b[0m";
46
+
47
+ console.log(` ${statusMark} ${type} \x1b[2m${age}\x1b[0m ${dir}`);
48
+ if (ideaId) console.log(` \x1b[2midea: ${ideaId}\x1b[0m`);
49
+ }
50
+ console.log();
51
+ }
52
+
53
+ export async function showTask(args) {
54
+ const id = args[0];
55
+ if (!id) {
56
+ console.log("Usage: cc task <id>");
57
+ return;
58
+ }
59
+ const t = await get(`/api/agent/tasks/${id}`);
60
+ if (!t) {
61
+ console.log(`Task not found: ${id}`);
62
+ return;
63
+ }
64
+
65
+ console.log();
66
+ console.log(`\x1b[1m TASK ${t.id}\x1b[0m`);
67
+ console.log(` ${"─".repeat(60)}`);
68
+ console.log(` Status: ${t.status}`);
69
+ console.log(` Type: ${t.type || t.phase || "?"}`);
70
+ console.log(` Direction: ${(t.direction || t.description || "").slice(0, 200)}`);
71
+ if (t.context?.idea_id) console.log(` Idea: ${t.context.idea_id}`);
72
+ if (t.worker_id) console.log(` Worker: ${t.worker_id}`);
73
+ if (t.result) console.log(` Result: ${String(t.result).slice(0, 200)}`);
74
+ console.log();
75
+ }
76
+
77
+ export async function claimTask(args) {
78
+ const id = args[0];
79
+ if (!id) {
80
+ // Claim next available
81
+ return claimNext();
82
+ }
83
+
84
+ const nid = nodeId();
85
+ const result = await patch(`/api/agent/tasks/${id}`, {
86
+ status: "running",
87
+ worker_id: `${hostname()}:cc-cli`,
88
+ });
89
+
90
+ if (result) {
91
+ console.log(`\x1b[32m✓\x1b[0m Claimed task ${id}`);
92
+ console.log(` Type: ${result.type || result.phase || "?"}`);
93
+ console.log(` Direction: ${(result.direction || result.description || "").slice(0, 150)}`);
94
+ } else {
95
+ console.log(`\x1b[31m✗\x1b[0m Failed to claim task ${id}`);
96
+ }
97
+ }
98
+
99
+ export async function claimNext() {
100
+ const data = await get("/api/agent/tasks", { status: "pending", limit: 1 });
101
+ const tasks = data?.tasks || (Array.isArray(data) ? data : []);
102
+
103
+ if (!tasks.length) {
104
+ console.log("No pending tasks available.");
105
+ return null;
106
+ }
107
+
108
+ const task = tasks[0];
109
+ const result = await patch(`/api/agent/tasks/${task.id}`, {
110
+ status: "running",
111
+ worker_id: `${hostname()}:cc-cli`,
112
+ });
113
+
114
+ if (result) {
115
+ console.log(`\x1b[32m✓\x1b[0m Claimed next task: ${task.id}`);
116
+ console.log(` Type: ${task.type || task.phase || "?"}`);
117
+ console.log(` Direction: ${(task.direction || task.description || "").slice(0, 150)}`);
118
+ // Output JSON for machine consumption when piped
119
+ if (!process.stdout.isTTY) {
120
+ console.log(JSON.stringify(task));
121
+ }
122
+ return task;
123
+ } else {
124
+ console.log(`\x1b[31m✗\x1b[0m Failed to claim task ${task.id}`);
125
+ return null;
126
+ }
127
+ }
128
+
129
+ export async function reportTask(args) {
130
+ const [id, status, ...outputParts] = args;
131
+ const output = outputParts.join(" ");
132
+
133
+ if (!id || !status) {
134
+ console.log("Usage: cc task report <id> <completed|failed> [output text]");
135
+ return;
136
+ }
137
+
138
+ if (!["completed", "failed"].includes(status)) {
139
+ console.log(`Status must be 'completed' or 'failed', got: ${status}`);
140
+ return;
141
+ }
142
+
143
+ const result = await patch(`/api/agent/tasks/${id}`, {
144
+ status,
145
+ result: output || `Task ${status} by ${hostname()}`,
146
+ });
147
+
148
+ if (result) {
149
+ const mark = status === "completed" ? "\x1b[32m✓\x1b[0m" : "\x1b[31m✗\x1b[0m";
150
+ console.log(`${mark} Reported task ${id} as ${status}`);
151
+ } else {
152
+ console.log(`\x1b[31m✗\x1b[0m Failed to report task ${id}`);
153
+ }
154
+ }
155
+
156
+ export async function seedTask(args) {
157
+ const [ideaId, type] = args;
158
+ const taskType = type || "spec";
159
+
160
+ if (!ideaId) {
161
+ console.log("Usage: cc task seed <idea_id> [spec|test|impl|review]");
162
+ return;
163
+ }
164
+
165
+ // Fetch idea for context
166
+ const idea = await get(`/api/ideas/${ideaId}`);
167
+ if (!idea) {
168
+ console.log(`Idea not found: ${ideaId}`);
169
+ return;
170
+ }
171
+
172
+ const result = await post("/api/agent/tasks", {
173
+ task_type: taskType,
174
+ direction: `${taskType} for '${idea.name}' (${ideaId})`,
175
+ context: {
176
+ idea_id: ideaId,
177
+ idea_name: idea.name,
178
+ seeded_by: `${hostname()}:cc-cli`,
179
+ },
180
+ });
181
+
182
+ if (result?.id) {
183
+ console.log(`\x1b[32m✓\x1b[0m Seeded ${taskType} task: ${result.id}`);
184
+ console.log(` Idea: ${idea.name}`);
185
+ } else {
186
+ console.log(`\x1b[31m✗\x1b[0m Failed to seed task`);
187
+ }
188
+ }
189
+
190
+ function timeSince(iso) {
191
+ const ms = Date.now() - new Date(iso).getTime();
192
+ const min = Math.floor(ms / 60000);
193
+ if (min < 1) return "now";
194
+ if (min < 60) return `${min}m`;
195
+ const hrs = Math.floor(min / 60);
196
+ if (hrs < 24) return `${hrs}h`;
197
+ return `${Math.floor(hrs / 24)}d`;
198
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "coherence-cli",
3
- "version": "0.4.1",
4
- "description": "Coherence Network CLI trace ideas from inception to payout, with fair attribution, coherence scoring, and 37 identity providers. No signup needed.",
3
+ "version": "0.6.1",
4
+ "description": "Coherence Network CLI \u2014 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
7
  "cc": "bin/cc.mjs",
@@ -47,4 +47,4 @@
47
47
  "url": "https://github.com/seeker71/Coherence-Network/issues"
48
48
  },
49
49
  "author": "Coherence Network Contributors"
50
- }
50
+ }