cnagent 2.0.7 → 2.1.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/cn +12 -361
- package/package.json +6 -4
- package/tools/dist/cn.js +32097 -0
- package/{dist → tools/dist}/inbox.js +285 -272
- package/tools/dist/peer-sync.js +24150 -0
package/bin/cn
CHANGED
|
@@ -1,364 +1,15 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# cn - Coherent Network agent CLI
|
|
3
|
+
# OCaml source: tools/src/cn/
|
|
4
|
+
# Bundled JS: tools/dist/cn.js
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*
|
|
6
|
-
* This is a thin wrapper that routes to the appropriate tool.
|
|
7
|
-
* OCaml source in tools/src/cn/, bundled JS in dist/cn.js
|
|
8
|
-
*/
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
+
DIST="$SCRIPT_DIR/../tools/dist/cn.js"
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
if [ ! -f "$DIST" ]; then
|
|
10
|
+
echo "Error: tools/dist/cn.js not found."
|
|
11
|
+
echo "Run 'npm run build' in the cn-agent directory first."
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
// Colors (respects NO_COLOR)
|
|
17
|
-
const noColor = process.env.NO_COLOR !== undefined;
|
|
18
|
-
const c = {
|
|
19
|
-
reset: noColor ? '' : '\x1b[0m',
|
|
20
|
-
green: noColor ? '' : '\x1b[32m',
|
|
21
|
-
red: noColor ? '' : '\x1b[31m',
|
|
22
|
-
yellow: noColor ? '' : '\x1b[33m',
|
|
23
|
-
cyan: noColor ? '' : '\x1b[36m',
|
|
24
|
-
magenta: noColor ? '' : '\x1b[35m',
|
|
25
|
-
dim: noColor ? '' : '\x1b[2m',
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const ok = (msg) => console.log(`${c.green}✓ ${msg}${c.reset}`);
|
|
29
|
-
const fail = (msg) => console.log(`${c.red}✗ ${msg}${c.reset}`);
|
|
30
|
-
const info = (msg) => console.log(`${c.cyan}${msg}${c.reset}`);
|
|
31
|
-
const warn = (msg) => console.log(`${c.yellow}⚠ ${msg}${c.reset}`);
|
|
32
|
-
const cmd = (msg) => `${c.magenta}${msg}${c.reset}`;
|
|
33
|
-
|
|
34
|
-
const HELP = `cn - Coherent Network agent CLI
|
|
35
|
-
|
|
36
|
-
Usage: cn <command> [options]
|
|
37
|
-
|
|
38
|
-
Commands:
|
|
39
|
-
init [name] Create new hub
|
|
40
|
-
status Show hub state
|
|
41
|
-
inbox Manage inbound messages
|
|
42
|
-
check List inbound branches
|
|
43
|
-
process Materialize as threads
|
|
44
|
-
flush Execute decisions
|
|
45
|
-
peer Manage peers
|
|
46
|
-
doctor Check system health
|
|
47
|
-
update Update cn to latest version
|
|
48
|
-
|
|
49
|
-
Aliases:
|
|
50
|
-
i = inbox, s = status, d = doctor
|
|
51
|
-
|
|
52
|
-
Flags:
|
|
53
|
-
--help, -h Show help
|
|
54
|
-
--version, -V Show version
|
|
55
|
-
|
|
56
|
-
Examples:
|
|
57
|
-
cn init sigma Create hub named 'sigma'
|
|
58
|
-
cn inbox check List inbound branches
|
|
59
|
-
cn doctor Check system health
|
|
60
|
-
`;
|
|
61
|
-
|
|
62
|
-
// Expand aliases
|
|
63
|
-
const aliases = { i: 'inbox', s: 'status', d: 'doctor', p: 'peer', t: 'thread' };
|
|
64
|
-
const expandAlias = (cmd) => aliases[cmd] || cmd;
|
|
65
|
-
|
|
66
|
-
// Find hub path
|
|
67
|
-
function findHubPath() {
|
|
68
|
-
let dir = process.cwd();
|
|
69
|
-
while (dir !== '/') {
|
|
70
|
-
if (fs.existsSync(path.join(dir, '.cn', 'config.json'))) {
|
|
71
|
-
return dir;
|
|
72
|
-
}
|
|
73
|
-
// Also check for cn-* pattern with state/peers.md
|
|
74
|
-
if (fs.existsSync(path.join(dir, 'state', 'peers.md'))) {
|
|
75
|
-
return dir;
|
|
76
|
-
}
|
|
77
|
-
dir = path.dirname(dir);
|
|
78
|
-
}
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Derive name from path
|
|
83
|
-
function deriveName(hubPath) {
|
|
84
|
-
const base = path.basename(hubPath);
|
|
85
|
-
return base.startsWith('cn-') ? base.slice(3) : base;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Run inbox tool
|
|
89
|
-
function runInbox(subCmd, hubPath, name) {
|
|
90
|
-
const inboxJs = path.join(__dirname, '..', 'dist', 'inbox.js');
|
|
91
|
-
if (!fs.existsSync(inboxJs)) {
|
|
92
|
-
fail('inbox.js not found. Run from cn-agent directory or install globally.');
|
|
93
|
-
process.exit(1);
|
|
94
|
-
}
|
|
95
|
-
const args = [inboxJs, subCmd, hubPath, name];
|
|
96
|
-
const result = spawn('node', args, { stdio: 'inherit' });
|
|
97
|
-
result.on('close', (code) => process.exit(code));
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Doctor command
|
|
101
|
-
function doctor(hubPath) {
|
|
102
|
-
console.log(`cn v${VERSION}`);
|
|
103
|
-
info(`Checking health...`);
|
|
104
|
-
console.log('');
|
|
105
|
-
|
|
106
|
-
let checks = [];
|
|
107
|
-
let warnings = [];
|
|
108
|
-
|
|
109
|
-
// Git
|
|
110
|
-
try {
|
|
111
|
-
const gitVersion = execSync('git --version', { encoding: 'utf8' }).trim();
|
|
112
|
-
checks.push({ name: 'git', ok: true, val: gitVersion.replace('git version ', '') });
|
|
113
|
-
} catch {
|
|
114
|
-
checks.push({ name: 'git', ok: false, val: 'not installed' });
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Git config
|
|
118
|
-
try {
|
|
119
|
-
const userName = execSync('git config user.name', { encoding: 'utf8' }).trim();
|
|
120
|
-
checks.push({ name: 'git user.name', ok: true, val: userName });
|
|
121
|
-
} catch {
|
|
122
|
-
checks.push({ name: 'git user.name', ok: false, val: 'not set' });
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
try {
|
|
126
|
-
const userEmail = execSync('git config user.email', { encoding: 'utf8' }).trim();
|
|
127
|
-
checks.push({ name: 'git user.email', ok: true, val: userEmail });
|
|
128
|
-
} catch {
|
|
129
|
-
checks.push({ name: 'git user.email', ok: false, val: 'not set' });
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Hub directory
|
|
133
|
-
checks.push({ name: 'hub directory', ok: fs.existsSync(hubPath), val: fs.existsSync(hubPath) ? 'exists' : 'not found' });
|
|
134
|
-
|
|
135
|
-
// .cn/config.json
|
|
136
|
-
const configPath = path.join(hubPath, '.cn', 'config.json');
|
|
137
|
-
checks.push({ name: '.cn/config.json', ok: fs.existsSync(configPath), val: fs.existsSync(configPath) ? 'exists' : 'missing' });
|
|
138
|
-
|
|
139
|
-
// spec/SOUL.md
|
|
140
|
-
const soulPath = path.join(hubPath, 'spec', 'SOUL.md');
|
|
141
|
-
if (fs.existsSync(soulPath)) {
|
|
142
|
-
checks.push({ name: 'spec/SOUL.md', ok: true, val: 'exists' });
|
|
143
|
-
} else {
|
|
144
|
-
warnings.push({ name: 'spec/SOUL.md', val: 'missing (optional)' });
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// state/peers.md
|
|
148
|
-
const peersPath = path.join(hubPath, 'state', 'peers.md');
|
|
149
|
-
if (fs.existsSync(peersPath)) {
|
|
150
|
-
const content = fs.readFileSync(peersPath, 'utf8');
|
|
151
|
-
const peerCount = (content.match(/- name:/g) || []).length;
|
|
152
|
-
checks.push({ name: 'state/peers.md', ok: true, val: `${peerCount} peer(s)` });
|
|
153
|
-
} else {
|
|
154
|
-
checks.push({ name: 'state/peers.md', ok: false, val: 'missing' });
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// origin remote
|
|
158
|
-
try {
|
|
159
|
-
execSync('git remote get-url origin', { cwd: hubPath, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
160
|
-
checks.push({ name: 'origin remote', ok: true, val: 'configured' });
|
|
161
|
-
} catch {
|
|
162
|
-
checks.push({ name: 'origin remote', ok: false, val: 'not configured' });
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// inbox status
|
|
166
|
-
try {
|
|
167
|
-
const result = execSync(`node ${path.join(__dirname, '..', 'dist', 'inbox.js')} check ${hubPath} ${deriveName(hubPath)} 2>&1`, { encoding: 'utf8' });
|
|
168
|
-
const inboundMatch = result.match(/(\d+) inbound/);
|
|
169
|
-
if (inboundMatch) {
|
|
170
|
-
const count = parseInt(inboundMatch[1]);
|
|
171
|
-
if (count > 0) {
|
|
172
|
-
warnings.push({ name: 'inbox', val: `${count} pending` });
|
|
173
|
-
} else {
|
|
174
|
-
checks.push({ name: 'inbox', ok: true, val: 'clear' });
|
|
175
|
-
}
|
|
176
|
-
} else if (result.includes('All clear')) {
|
|
177
|
-
checks.push({ name: 'inbox', ok: true, val: 'clear' });
|
|
178
|
-
}
|
|
179
|
-
} catch {
|
|
180
|
-
warnings.push({ name: 'inbox', val: 'check failed' });
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Print checks
|
|
184
|
-
const width = 22;
|
|
185
|
-
checks.forEach(({ name, ok: isOk, val }) => {
|
|
186
|
-
const dots = '.'.repeat(Math.max(1, width - name.length));
|
|
187
|
-
const status = isOk
|
|
188
|
-
? `${c.green}✓ ${val}${c.reset}`
|
|
189
|
-
: `${c.red}✗ ${val}${c.reset}`;
|
|
190
|
-
console.log(`${name}${dots} ${status}`);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
// Print warnings
|
|
194
|
-
warnings.forEach(({ name, val }) => {
|
|
195
|
-
const dots = '.'.repeat(Math.max(1, width - name.length));
|
|
196
|
-
console.log(`${name}${dots} ${c.yellow}⚠ ${val}${c.reset}`);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
console.log('');
|
|
200
|
-
const fails = checks.filter(c => !c.ok).length;
|
|
201
|
-
const warns = warnings.length;
|
|
202
|
-
|
|
203
|
-
if (fails === 0) {
|
|
204
|
-
ok('All critical checks passed.');
|
|
205
|
-
} else {
|
|
206
|
-
fail(`${fails} issue(s) found.`);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
console.log(`${c.dim}[status] ok=${checks.filter(c=>c.ok).length} warn=${warns} fail=${fails} version=${VERSION}${c.reset}`);
|
|
210
|
-
process.exit(fails > 0 ? 1 : 0);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Status command
|
|
214
|
-
function status(hubPath, name) {
|
|
215
|
-
info(`cn hub: ${name}`);
|
|
216
|
-
console.log('');
|
|
217
|
-
console.log(`hub..................... ${c.green}✓${c.reset}`);
|
|
218
|
-
console.log(`name.................... ${c.green}✓ ${name}${c.reset}`);
|
|
219
|
-
console.log(`path.................... ${c.green}✓ ${hubPath}${c.reset}`);
|
|
220
|
-
console.log('');
|
|
221
|
-
console.log(`${c.dim}[status] ok version=${VERSION}${c.reset}`);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Main
|
|
225
|
-
const args = process.argv.slice(2);
|
|
226
|
-
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
227
|
-
console.log(HELP);
|
|
228
|
-
process.exit(0);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (args[0] === '--version' || args[0] === '-V') {
|
|
232
|
-
console.log(`cn ${VERSION}`);
|
|
233
|
-
process.exit(0);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const command = expandAlias(args[0]);
|
|
237
|
-
const subArgs = args.slice(1);
|
|
238
|
-
|
|
239
|
-
// Commands that don't need hub
|
|
240
|
-
if (command === 'init') {
|
|
241
|
-
warn('cn init not yet implemented');
|
|
242
|
-
process.exit(1);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (command === 'update') {
|
|
246
|
-
// Check if already on latest
|
|
247
|
-
try {
|
|
248
|
-
const latest = execSync('npm view cnagent version', { encoding: 'utf8' }).trim();
|
|
249
|
-
if (VERSION === latest) {
|
|
250
|
-
ok(`Already up to date (v${VERSION})`);
|
|
251
|
-
// Still write runtime.md if in a hub
|
|
252
|
-
writeRuntimeMd(findHubPath(), latest);
|
|
253
|
-
process.exit(0);
|
|
254
|
-
}
|
|
255
|
-
info(`Updating cnagent v${VERSION} → v${latest}...`);
|
|
256
|
-
execSync('npm install -g cnagent@latest', { stdio: 'inherit' });
|
|
257
|
-
ok(`Updated to v${latest}`);
|
|
258
|
-
// Write runtime.md if in a hub
|
|
259
|
-
writeRuntimeMd(findHubPath(), latest);
|
|
260
|
-
} catch (e) {
|
|
261
|
-
fail('Update failed. Try: npm install -g cnagent@latest');
|
|
262
|
-
process.exit(1);
|
|
263
|
-
}
|
|
264
|
-
process.exit(0);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Write state/runtime.md after update
|
|
268
|
-
function writeRuntimeMd(hubPath, cnVersion) {
|
|
269
|
-
if (!hubPath) return; // Not in a hub, skip
|
|
270
|
-
|
|
271
|
-
const runtimePath = path.join(hubPath, 'state', 'runtime.md');
|
|
272
|
-
const stateDir = path.join(hubPath, 'state');
|
|
273
|
-
|
|
274
|
-
// Ensure state/ exists
|
|
275
|
-
if (!fs.existsSync(stateDir)) {
|
|
276
|
-
fs.mkdirSync(stateDir, { recursive: true });
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Get template info
|
|
280
|
-
let templateVersion = 'unknown';
|
|
281
|
-
let templateCommit = 'unknown';
|
|
282
|
-
try {
|
|
283
|
-
const pkgPath = path.join(__dirname, '..', 'package.json');
|
|
284
|
-
if (fs.existsSync(pkgPath)) {
|
|
285
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
286
|
-
templateVersion = pkg.version || 'unknown';
|
|
287
|
-
}
|
|
288
|
-
templateCommit = execSync('git rev-parse --short HEAD 2>/dev/null || echo unknown', {
|
|
289
|
-
encoding: 'utf8',
|
|
290
|
-
cwd: hubPath
|
|
291
|
-
}).trim();
|
|
292
|
-
} catch {}
|
|
293
|
-
|
|
294
|
-
// Gather runtime info
|
|
295
|
-
const nodeVersion = process.version;
|
|
296
|
-
const platform = `${process.platform} ${process.arch}`;
|
|
297
|
-
const hubName = deriveName(hubPath);
|
|
298
|
-
|
|
299
|
-
let peerCount = 0;
|
|
300
|
-
try {
|
|
301
|
-
const peersPath = path.join(hubPath, 'state', 'peers.md');
|
|
302
|
-
if (fs.existsSync(peersPath)) {
|
|
303
|
-
const content = fs.readFileSync(peersPath, 'utf8');
|
|
304
|
-
peerCount = (content.match(/- name:/g) || []).length;
|
|
305
|
-
}
|
|
306
|
-
} catch {}
|
|
307
|
-
|
|
308
|
-
const content = `# Runtime State
|
|
309
|
-
|
|
310
|
-
Auto-generated by \`cn update\`. Do not edit manually.
|
|
311
|
-
|
|
312
|
-
\`\`\`yaml
|
|
313
|
-
session_start: ${new Date().toISOString()}
|
|
314
|
-
cn_version: ${cnVersion}
|
|
315
|
-
template_version: ${templateVersion}
|
|
316
|
-
template_commit: ${templateCommit}
|
|
317
|
-
node_version: ${nodeVersion}
|
|
318
|
-
platform: ${platform}
|
|
319
|
-
hub_name: ${hubName}
|
|
320
|
-
hub_path: ${hubPath}
|
|
321
|
-
peer_count: ${peerCount}
|
|
322
|
-
\`\`\`
|
|
323
|
-
`;
|
|
324
|
-
|
|
325
|
-
fs.writeFileSync(runtimePath, content);
|
|
326
|
-
info(`Wrote ${runtimePath}`);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Find hub
|
|
330
|
-
const hubPath = findHubPath();
|
|
331
|
-
if (!hubPath) {
|
|
332
|
-
fail('Not in a cn hub.');
|
|
333
|
-
console.log('');
|
|
334
|
-
console.log('Either:');
|
|
335
|
-
console.log(` 1) ${cmd('cd')} into an existing hub (cn-sigma, cn-pi, etc.)`);
|
|
336
|
-
console.log(` 2) ${cmd('cn init <name>')} to create a new one`);
|
|
337
|
-
process.exit(1);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const name = deriveName(hubPath);
|
|
341
|
-
|
|
342
|
-
switch (command) {
|
|
343
|
-
case 'status':
|
|
344
|
-
status(hubPath, name);
|
|
345
|
-
break;
|
|
346
|
-
|
|
347
|
-
case 'doctor':
|
|
348
|
-
doctor(hubPath);
|
|
349
|
-
break;
|
|
350
|
-
|
|
351
|
-
case 'inbox':
|
|
352
|
-
const subCmd = subArgs[0] || 'check';
|
|
353
|
-
if (!['check', 'process', 'flush'].includes(subCmd)) {
|
|
354
|
-
fail(`Unknown inbox command: ${subCmd}`);
|
|
355
|
-
process.exit(1);
|
|
356
|
-
}
|
|
357
|
-
runInbox(subCmd, hubPath, name);
|
|
358
|
-
break;
|
|
359
|
-
|
|
360
|
-
default:
|
|
361
|
-
fail(`Unknown command: ${command}`);
|
|
362
|
-
console.log(`Run ${cmd('cn --help')} for usage.`);
|
|
363
|
-
process.exit(1);
|
|
364
|
-
}
|
|
15
|
+
exec node "$DIST" "$@"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cnagent",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Coherent Network agent CLI — everything runs through cn",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cn-agent",
|
|
@@ -22,12 +22,14 @@
|
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"bin/",
|
|
25
|
-
"dist/"
|
|
25
|
+
"tools/dist/"
|
|
26
26
|
],
|
|
27
27
|
"scripts": {
|
|
28
28
|
"build": "dune build && npm run bundle",
|
|
29
|
-
"bundle": "
|
|
30
|
-
"
|
|
29
|
+
"bundle": "npm run bundle:inbox && npm run bundle:cn",
|
|
30
|
+
"bundle:inbox": "npx esbuild _build/default/tools/src/inbox/output/tools/src/inbox/inbox.js --bundle --platform=node --outfile=tools/dist/inbox.js",
|
|
31
|
+
"bundle:cn": "npx esbuild _build/default/tools/src/cn/output/tools/src/cn/cn.js --bundle --platform=node --outfile=tools/dist/cn.js",
|
|
32
|
+
"test": "dune runtest"
|
|
31
33
|
},
|
|
32
34
|
"author": "usurobor",
|
|
33
35
|
"license": "Apache-2.0",
|