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 +57 -3
- package/README.template.md +57 -3
- package/bin/cc.mjs +88 -1
- package/lib/api.mjs +15 -0
- package/lib/commands/nodes.mjs +57 -1
- package/lib/commands/tasks.mjs +198 -0
- package/package.json +3 -3
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
|
|
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
|
-
|
|
158
|
-
|
|
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
|
---
|
package/README.template.md
CHANGED
|
@@ -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
|
|
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
|
-
|
|
138
|
-
|
|
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), {
|
package/lib/commands/nodes.mjs
CHANGED
|
@@ -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
|
-
"description": "Coherence Network CLI
|
|
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
|
+
}
|