coherence-cli 0.6.1 → 0.7.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/bin/cc.mjs CHANGED
@@ -15,6 +15,7 @@ import { showStatus, showResonance } from "../lib/commands/status.mjs";
15
15
  import { showIdentity, linkIdentity, unlinkIdentity, lookupIdentity, setupIdentity, setIdentity } from "../lib/commands/identity.mjs";
16
16
  import { listNodes, sendMessage, readMessages, sendCommand } from "../lib/commands/nodes.mjs";
17
17
  import { listTasks, showTask, claimTask, claimNext, reportTask, seedTask } from "../lib/commands/tasks.mjs";
18
+ import { setup } from "../lib/commands/setup.mjs";
18
19
 
19
20
  // Version check — non-blocking, runs in background
20
21
  const require = createRequire(import.meta.url);
@@ -69,6 +70,7 @@ const COMMANDS = {
69
70
  inbox: () => readMessages(args),
70
71
  tasks: () => listTasks(args),
71
72
  task: () => handleTask(args),
73
+ setup: () => setup(args),
72
74
  update: () => selfUpdate(),
73
75
  version: () => console.log(`cc v${LOCAL_VERSION}`),
74
76
  help: () => showHelp(),
@@ -52,10 +52,32 @@ export async function sendCommand(args) {
52
52
  });
53
53
 
54
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
- );
55
+ const msgId = result.id;
56
+ console.log(`\x1b[32m✓\x1b[0m Command sent (msg ${msgId.slice(0, 12)})`);
57
+ console.log(" Waiting for reply (up to 3 min)...");
58
+
59
+ // Poll for reply
60
+ const deadline = Date.now() + 180_000;
61
+ const pollInterval = 10_000;
62
+ while (Date.now() < deadline) {
63
+ await new Promise((r) => setTimeout(r, pollInterval));
64
+ process.stdout.write(".");
65
+
66
+ const inbox = await get(
67
+ `/api/federation/nodes/${myNodeId}/messages?unread_only=false&limit=20`,
68
+ );
69
+ const reply = inbox?.messages?.find(
70
+ (m) =>
71
+ (m.type === "command_response" || m.type === "ack") &&
72
+ m.payload?.in_reply_to === msgId,
73
+ );
74
+ if (reply) {
75
+ console.log(`\n\x1b[32m✓\x1b[0m Reply from ${node.hostname}:`);
76
+ console.log(` ${reply.text}`);
77
+ return;
78
+ }
79
+ }
80
+ console.log("\n\x1b[33m⏱\x1b[0m No reply within 3 min. Check later: cc inbox");
59
81
  } else {
60
82
  console.log("\x1b[31m✗\x1b[0m Failed to send command");
61
83
  }
@@ -142,15 +164,45 @@ export async function sendMessage(args) {
142
164
  console.log("\x1b[31m✗\x1b[0m Failed to broadcast");
143
165
  }
144
166
  } else {
167
+ // Resolve target name to node_id
168
+ const targetNode = Array.isArray(nodes)
169
+ ? nodes.find(
170
+ (n) =>
171
+ n.node_id?.startsWith(targetOrBroadcast) ||
172
+ n.hostname?.toLowerCase().includes(targetOrBroadcast.toLowerCase()),
173
+ )
174
+ : null;
175
+ const toNodeId = targetNode?.node_id || targetOrBroadcast;
176
+ const toName = targetNode?.hostname || targetOrBroadcast.slice(0, 12);
177
+
145
178
  const result = await post(`/api/federation/nodes/${myNodeId}/messages`, {
146
179
  from_node: myNodeId,
147
- to_node: targetOrBroadcast,
180
+ to_node: toNodeId,
148
181
  type: "text",
149
182
  text,
150
183
  payload: {},
151
184
  });
152
- if (result) {
153
- console.log(`\x1b[32m✓\x1b[0m Message sent to ${targetOrBroadcast.slice(0, 12)}: ${text.slice(0, 60)}`);
185
+ if (result?.id) {
186
+ const msgId = result.id;
187
+ console.log(`\x1b[32m✓\x1b[0m Message sent to ${toName}: ${text.slice(0, 60)}`);
188
+ console.log(" Waiting for ack (up to 3 min)...");
189
+
190
+ const deadline = Date.now() + 180_000;
191
+ while (Date.now() < deadline) {
192
+ await new Promise((r) => setTimeout(r, 10_000));
193
+ process.stdout.write(".");
194
+ const inbox = await get(
195
+ `/api/federation/nodes/${myNodeId}/messages?unread_only=false&limit=20`,
196
+ );
197
+ const ack = inbox?.messages?.find(
198
+ (m) => m.type === "ack" && m.payload?.in_reply_to === msgId,
199
+ );
200
+ if (ack) {
201
+ console.log(`\n\x1b[32m✓\x1b[0m Acknowledged by ${toName}`);
202
+ return;
203
+ }
204
+ }
205
+ console.log("\n\x1b[33m⏱\x1b[0m No ack within 3 min. Node may be offline. Check: cc inbox");
154
206
  } else {
155
207
  console.log("\x1b[31m✗\x1b[0m Failed to send message");
156
208
  }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Interactive onboarding: cc setup
3
+ *
4
+ * Guides a new contributor through identity + API key setup.
5
+ * Non-interactive mode: cc setup --name <name> --provider <p> --id <id>
6
+ */
7
+
8
+ import { get, post } from "../api.mjs";
9
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { join } from "node:path";
12
+ import { createInterface } from "node:readline";
13
+
14
+ const CONFIG_DIR = join(homedir(), ".coherence-network");
15
+ const KEYS_FILE = join(CONFIG_DIR, "keys.json");
16
+
17
+ function ask(rl, question) {
18
+ return new Promise((resolve) => rl.question(question, resolve));
19
+ }
20
+
21
+ function loadKeys() {
22
+ try {
23
+ return JSON.parse(readFileSync(KEYS_FILE, "utf-8"));
24
+ } catch {
25
+ return {};
26
+ }
27
+ }
28
+
29
+ function saveKeys(keys) {
30
+ mkdirSync(CONFIG_DIR, { recursive: true });
31
+ writeFileSync(KEYS_FILE, JSON.stringify(keys, null, 2), { mode: 0o600 });
32
+ }
33
+
34
+ export async function setup(args) {
35
+ // Check if already set up
36
+ const keys = loadKeys();
37
+ if (keys.api_key && keys.contributor_id) {
38
+ console.log(`\n\x1b[32m✓\x1b[0m Already set up as \x1b[1m${keys.contributor_id}\x1b[0m`);
39
+ console.log(` API key: ${keys.api_key.slice(0, 12)}...`);
40
+ console.log(` Provider: ${keys.provider}:${keys.provider_id}`);
41
+ console.log(`\n To reconfigure: rm ${KEYS_FILE} && cc setup`);
42
+ return;
43
+ }
44
+
45
+ // Non-interactive mode
46
+ const nameIdx = args.indexOf("--name");
47
+ const providerIdx = args.indexOf("--provider");
48
+ const idIdx = args.indexOf("--id");
49
+
50
+ if (nameIdx >= 0 && providerIdx >= 0 && idIdx >= 0) {
51
+ const name = args[nameIdx + 1];
52
+ const provider = args[providerIdx + 1];
53
+ const providerId = args[idIdx + 1];
54
+ return await completeSetup(name, provider, providerId);
55
+ }
56
+
57
+ // Interactive mode
58
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
59
+
60
+ console.log(`
61
+ \x1b[1mCoherence Network — Contributor Setup\x1b[0m
62
+
63
+ This will create your contributor identity and generate a personal API key.
64
+ Everything you create, contribute, and invest will be attributed to you.
65
+ `);
66
+
67
+ const name = await ask(rl, " Your name (will be your contributor ID): ");
68
+ if (!name.trim()) {
69
+ console.log("\x1b[31m✗\x1b[0m Name is required");
70
+ rl.close();
71
+ return;
72
+ }
73
+
74
+ console.log(`
75
+ Link an identity so others can find and verify you.
76
+ Providers: github, email, ethereum, discord, x, linkedin, telegram, ...
77
+ `);
78
+
79
+ const provider = await ask(rl, " Provider (e.g. github): ");
80
+ const providerId = await ask(rl, ` Your ${provider || "provider"} handle/address: `);
81
+
82
+ rl.close();
83
+
84
+ if (!provider.trim() || !providerId.trim()) {
85
+ console.log("\x1b[31m✗\x1b[0m Provider and handle are required");
86
+ return;
87
+ }
88
+
89
+ await completeSetup(name.trim(), provider.trim().toLowerCase(), providerId.trim());
90
+ }
91
+
92
+ async function completeSetup(name, provider, providerId) {
93
+ console.log(`\n Setting up \x1b[1m${name}\x1b[0m with ${provider}:${providerId}...`);
94
+
95
+ // Generate API key
96
+ const result = await post("/api/auth/keys", {
97
+ contributor_id: name,
98
+ provider,
99
+ provider_id: providerId,
100
+ });
101
+
102
+ if (!result || !result.api_key) {
103
+ console.log(`\x1b[31m✗\x1b[0m Setup failed: ${JSON.stringify(result)}`);
104
+ return;
105
+ }
106
+
107
+ // Save to config
108
+ const keys = {
109
+ contributor_id: name,
110
+ api_key: result.api_key,
111
+ provider,
112
+ provider_id: providerId,
113
+ created_at: result.created_at,
114
+ scopes: result.scopes,
115
+ };
116
+ saveKeys(keys);
117
+
118
+ console.log(`
119
+ \x1b[32m✓\x1b[0m Setup complete!
120
+
121
+ Contributor: \x1b[1m${name}\x1b[0m
122
+ Identity: ${provider}:${providerId}
123
+ API key: ${result.api_key.slice(0, 20)}...
124
+ Saved to: ${KEYS_FILE}
125
+
126
+ You can now:
127
+ cc ideas Browse ideas
128
+ cc share Submit a new idea
129
+ cc contribute Record a contribution
130
+ cc stake <id> <cc> Invest in an idea
131
+ cc status Check network health
132
+ `);
133
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coherence-cli",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
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": {