society-protocol 1.0.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/LICENSE +21 -0
- package/README.md +111 -0
- package/dist/adapters.d.ts +101 -0
- package/dist/adapters.d.ts.map +1 -0
- package/dist/adapters.js +764 -0
- package/dist/adapters.js.map +1 -0
- package/dist/agents-md.d.ts +59 -0
- package/dist/agents-md.d.ts.map +1 -0
- package/dist/agents-md.js +204 -0
- package/dist/agents-md.js.map +1 -0
- package/dist/autoconfig.d.ts +137 -0
- package/dist/autoconfig.d.ts.map +1 -0
- package/dist/autoconfig.js +452 -0
- package/dist/autoconfig.js.map +1 -0
- package/dist/bootstrap.d.ts +68 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +304 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/bridges/a2a-bridge.d.ts +156 -0
- package/dist/bridges/a2a-bridge.d.ts.map +1 -0
- package/dist/bridges/a2a-bridge.js +337 -0
- package/dist/bridges/a2a-bridge.js.map +1 -0
- package/dist/bridges/mcp-bridge.d.ts +87 -0
- package/dist/bridges/mcp-bridge.d.ts.map +1 -0
- package/dist/bridges/mcp-bridge.js +332 -0
- package/dist/bridges/mcp-bridge.js.map +1 -0
- package/dist/cache.d.ts +130 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +257 -0
- package/dist/cache.js.map +1 -0
- package/dist/capsules.d.ts +23 -0
- package/dist/capsules.d.ts.map +1 -0
- package/dist/capsules.js +75 -0
- package/dist/capsules.js.map +1 -0
- package/dist/cli/commands.d.ts +8 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +263 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/coc.d.ts +121 -0
- package/dist/coc.d.ts.map +1 -0
- package/dist/coc.js +629 -0
- package/dist/coc.js.map +1 -0
- package/dist/coc.test.d.ts +2 -0
- package/dist/coc.test.d.ts.map +1 -0
- package/dist/coc.test.js +80 -0
- package/dist/coc.test.js.map +1 -0
- package/dist/compression.d.ts +125 -0
- package/dist/compression.d.ts.map +1 -0
- package/dist/compression.js +573 -0
- package/dist/compression.js.map +1 -0
- package/dist/cot-stream.d.ts +220 -0
- package/dist/cot-stream.d.ts.map +1 -0
- package/dist/cot-stream.js +673 -0
- package/dist/cot-stream.js.map +1 -0
- package/dist/crypto-wasm.d.ts +100 -0
- package/dist/crypto-wasm.d.ts.map +1 -0
- package/dist/crypto-wasm.js +229 -0
- package/dist/crypto-wasm.js.map +1 -0
- package/dist/federation.d.ts +200 -0
- package/dist/federation.d.ts.map +1 -0
- package/dist/federation.js +691 -0
- package/dist/federation.js.map +1 -0
- package/dist/federation.test.d.ts +2 -0
- package/dist/federation.test.d.ts.map +1 -0
- package/dist/federation.test.js +71 -0
- package/dist/federation.test.js.map +1 -0
- package/dist/gateway/capability-router.d.ts +77 -0
- package/dist/gateway/capability-router.d.ts.map +1 -0
- package/dist/gateway/capability-router.js +222 -0
- package/dist/gateway/capability-router.js.map +1 -0
- package/dist/gateway/demand-spawner.d.ts +155 -0
- package/dist/gateway/demand-spawner.d.ts.map +1 -0
- package/dist/gateway/demand-spawner.js +426 -0
- package/dist/gateway/demand-spawner.js.map +1 -0
- package/dist/identity.d.ts +46 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +102 -0
- package/dist/identity.js.map +1 -0
- package/dist/identity.test.d.ts +2 -0
- package/dist/identity.test.d.ts.map +1 -0
- package/dist/identity.test.js +45 -0
- package/dist/identity.test.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1572 -0
- package/dist/index.js.map +1 -0
- package/dist/integration.d.ts +210 -0
- package/dist/integration.d.ts.map +1 -0
- package/dist/integration.js +1105 -0
- package/dist/integration.js.map +1 -0
- package/dist/integration.test.d.ts +2 -0
- package/dist/integration.test.d.ts.map +1 -0
- package/dist/integration.test.js +155 -0
- package/dist/integration.test.js.map +1 -0
- package/dist/knowledge.d.ts +219 -0
- package/dist/knowledge.d.ts.map +1 -0
- package/dist/knowledge.js +543 -0
- package/dist/knowledge.js.map +1 -0
- package/dist/knowledge.test.d.ts +2 -0
- package/dist/knowledge.test.d.ts.map +1 -0
- package/dist/knowledge.test.js +72 -0
- package/dist/knowledge.test.js.map +1 -0
- package/dist/latent-space.d.ts +178 -0
- package/dist/latent-space.d.ts.map +1 -0
- package/dist/latent-space.js +385 -0
- package/dist/latent-space.js.map +1 -0
- package/dist/lib.d.ts +30 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +30 -0
- package/dist/lib.js.map +1 -0
- package/dist/mcp/server.d.ts +74 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +1392 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/metrics.d.ts +98 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +222 -0
- package/dist/metrics.js.map +1 -0
- package/dist/p2p.d.ts +87 -0
- package/dist/p2p.d.ts.map +1 -0
- package/dist/p2p.js +606 -0
- package/dist/p2p.js.map +1 -0
- package/dist/persona/capabilities.d.ts +17 -0
- package/dist/persona/capabilities.d.ts.map +1 -0
- package/dist/persona/capabilities.js +224 -0
- package/dist/persona/capabilities.js.map +1 -0
- package/dist/persona/domains.d.ts +22 -0
- package/dist/persona/domains.d.ts.map +1 -0
- package/dist/persona/domains.js +176 -0
- package/dist/persona/domains.js.map +1 -0
- package/dist/persona/embeddings.d.ts +40 -0
- package/dist/persona/embeddings.d.ts.map +1 -0
- package/dist/persona/embeddings.js +265 -0
- package/dist/persona/embeddings.js.map +1 -0
- package/dist/persona/engine.d.ts +79 -0
- package/dist/persona/engine.d.ts.map +1 -0
- package/dist/persona/engine.js +1087 -0
- package/dist/persona/engine.js.map +1 -0
- package/dist/persona/index.d.ts +11 -0
- package/dist/persona/index.d.ts.map +1 -0
- package/dist/persona/index.js +11 -0
- package/dist/persona/index.js.map +1 -0
- package/dist/persona/lifecycle.d.ts +17 -0
- package/dist/persona/lifecycle.d.ts.map +1 -0
- package/dist/persona/lifecycle.js +36 -0
- package/dist/persona/lifecycle.js.map +1 -0
- package/dist/persona/retrieval.d.ts +6 -0
- package/dist/persona/retrieval.d.ts.map +1 -0
- package/dist/persona/retrieval.js +122 -0
- package/dist/persona/retrieval.js.map +1 -0
- package/dist/persona/sync.d.ts +15 -0
- package/dist/persona/sync.d.ts.map +1 -0
- package/dist/persona/sync.js +92 -0
- package/dist/persona/sync.js.map +1 -0
- package/dist/persona/types.d.ts +283 -0
- package/dist/persona/types.d.ts.map +1 -0
- package/dist/persona/types.js +2 -0
- package/dist/persona/types.js.map +1 -0
- package/dist/persona/zkp/engine.d.ts +26 -0
- package/dist/persona/zkp/engine.d.ts.map +1 -0
- package/dist/persona/zkp/engine.js +370 -0
- package/dist/persona/zkp/engine.js.map +1 -0
- package/dist/persona/zkp/types.d.ts +39 -0
- package/dist/persona/zkp/types.d.ts.map +1 -0
- package/dist/persona/zkp/types.js +2 -0
- package/dist/persona/zkp/types.js.map +1 -0
- package/dist/planner.d.ts +114 -0
- package/dist/planner.d.ts.map +1 -0
- package/dist/planner.js +522 -0
- package/dist/planner.js.map +1 -0
- package/dist/proactive/checkpoints.d.ts +9 -0
- package/dist/proactive/checkpoints.d.ts.map +1 -0
- package/dist/proactive/checkpoints.js +20 -0
- package/dist/proactive/checkpoints.js.map +1 -0
- package/dist/proactive/engine.d.ts +59 -0
- package/dist/proactive/engine.d.ts.map +1 -0
- package/dist/proactive/engine.js +406 -0
- package/dist/proactive/engine.js.map +1 -0
- package/dist/proactive/scheduler.d.ts +11 -0
- package/dist/proactive/scheduler.d.ts.map +1 -0
- package/dist/proactive/scheduler.js +45 -0
- package/dist/proactive/scheduler.js.map +1 -0
- package/dist/proactive/swarm-controller.d.ts +189 -0
- package/dist/proactive/swarm-controller.d.ts.map +1 -0
- package/dist/proactive/swarm-controller.js +477 -0
- package/dist/proactive/swarm-controller.js.map +1 -0
- package/dist/proactive/swarm-registry.d.ts +13 -0
- package/dist/proactive/swarm-registry.d.ts.map +1 -0
- package/dist/proactive/swarm-registry.js +122 -0
- package/dist/proactive/swarm-registry.js.map +1 -0
- package/dist/proactive/types.d.ts +145 -0
- package/dist/proactive/types.d.ts.map +1 -0
- package/dist/proactive/types.js +25 -0
- package/dist/proactive/types.js.map +1 -0
- package/dist/registry.d.ts +35 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +88 -0
- package/dist/registry.js.map +1 -0
- package/dist/reputation.d.ts +123 -0
- package/dist/reputation.d.ts.map +1 -0
- package/dist/reputation.js +366 -0
- package/dist/reputation.js.map +1 -0
- package/dist/reputation.test.d.ts +5 -0
- package/dist/reputation.test.d.ts.map +1 -0
- package/dist/reputation.test.js +265 -0
- package/dist/reputation.test.js.map +1 -0
- package/dist/rooms.d.ts +96 -0
- package/dist/rooms.d.ts.map +1 -0
- package/dist/rooms.js +410 -0
- package/dist/rooms.js.map +1 -0
- package/dist/sdk/client.d.ts +290 -0
- package/dist/sdk/client.d.ts.map +1 -0
- package/dist/sdk/client.js +1287 -0
- package/dist/sdk/client.js.map +1 -0
- package/dist/sdk/index.d.ts +32 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +70 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/security.d.ts +230 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +652 -0
- package/dist/security.js.map +1 -0
- package/dist/skills/engine.d.ts +262 -0
- package/dist/skills/engine.d.ts.map +1 -0
- package/dist/skills/engine.js +788 -0
- package/dist/skills/engine.js.map +1 -0
- package/dist/skills/engine.test.d.ts +2 -0
- package/dist/skills/engine.test.d.ts.map +1 -0
- package/dist/skills/engine.test.js +134 -0
- package/dist/skills/engine.test.js.map +1 -0
- package/dist/skills/parser.d.ts +129 -0
- package/dist/skills/parser.d.ts.map +1 -0
- package/dist/skills/parser.js +318 -0
- package/dist/skills/parser.js.map +1 -0
- package/dist/social.d.ts +149 -0
- package/dist/social.d.ts.map +1 -0
- package/dist/social.js +401 -0
- package/dist/social.js.map +1 -0
- package/dist/storage-optimized.d.ts +116 -0
- package/dist/storage-optimized.d.ts.map +1 -0
- package/dist/storage-optimized.js +264 -0
- package/dist/storage-optimized.js.map +1 -0
- package/dist/storage.d.ts +584 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +2703 -0
- package/dist/storage.js.map +1 -0
- package/dist/storage.test.d.ts +2 -0
- package/dist/storage.test.d.ts.map +1 -0
- package/dist/storage.test.js +78 -0
- package/dist/storage.test.js.map +1 -0
- package/dist/swp.d.ts +443 -0
- package/dist/swp.d.ts.map +1 -0
- package/dist/swp.js +223 -0
- package/dist/swp.js.map +1 -0
- package/dist/swp.test.d.ts +5 -0
- package/dist/swp.test.d.ts.map +1 -0
- package/dist/swp.test.js +127 -0
- package/dist/swp.test.js.map +1 -0
- package/dist/templates.d.ts +25 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +1048 -0
- package/dist/templates.js.map +1 -0
- package/dist/test-e2e.d.ts +14 -0
- package/dist/test-e2e.d.ts.map +1 -0
- package/dist/test-e2e.js +266 -0
- package/dist/test-e2e.js.map +1 -0
- package/dist/workers/research-worker.d.ts +19 -0
- package/dist/workers/research-worker.d.ts.map +1 -0
- package/dist/workers/research-worker.js +141 -0
- package/dist/workers/research-worker.js.map +1 -0
- package/package.json +110 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1572 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Society Protocol — CLI v1.0 (State of the Art)
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* society node --name Alice --room lobby
|
|
7
|
+
* society node --name Bob --room lobby --bootstrap /ip4/127.0.0.1/tcp/12345/p2p/QmXxx
|
|
8
|
+
* society node --name Charlie --room lobby --relay --enable-gossipsub
|
|
9
|
+
*
|
|
10
|
+
* Interactive commands:
|
|
11
|
+
* /peers — list connected peers
|
|
12
|
+
* /rooms — list joined rooms
|
|
13
|
+
* /presence — list online peers with reputation
|
|
14
|
+
* /reputation [did] — show reputation scores
|
|
15
|
+
* /info — show node info
|
|
16
|
+
* /history — chat history
|
|
17
|
+
* /summon "goal" — start AI-planned collaboration
|
|
18
|
+
* /template <name> [goal] — use predefined template
|
|
19
|
+
* /chains — list active chains
|
|
20
|
+
* /chain <id> — show chain details
|
|
21
|
+
* /step <id> <status> — submit step result
|
|
22
|
+
* /assign <step> <agent> — manually assign step
|
|
23
|
+
* /review <step> <decision> — review a step
|
|
24
|
+
* /cancel <chain> — cancel chain
|
|
25
|
+
* /export <chain> — export capsule
|
|
26
|
+
* /cache — show planner cache stats
|
|
27
|
+
* /mesh-request ... — request federation peering
|
|
28
|
+
* /mesh-peerings ... — list peerings
|
|
29
|
+
* /mesh-open ... — open mesh bridge
|
|
30
|
+
* /mesh-bridges — list mesh bridges
|
|
31
|
+
* /mesh-stats — show mesh metrics
|
|
32
|
+
* /debug — toggle debug mode
|
|
33
|
+
* /quit — exit
|
|
34
|
+
*/
|
|
35
|
+
import { Command } from 'commander';
|
|
36
|
+
import readline from 'readline';
|
|
37
|
+
import { toString as uint8ToString } from 'uint8arrays';
|
|
38
|
+
import { generateIdentity, restoreIdentity } from './identity.js';
|
|
39
|
+
import { Storage } from './storage.js';
|
|
40
|
+
import { P2PNode } from './p2p.js';
|
|
41
|
+
import { RoomManager } from './rooms.js';
|
|
42
|
+
import { CocEngine } from './coc.js';
|
|
43
|
+
import { Planner } from './planner.js';
|
|
44
|
+
import { ReputationEngine, formatReputationTier } from './reputation.js';
|
|
45
|
+
import { AdapterHost } from './adapters.js';
|
|
46
|
+
import { getTemplate, TEMPLATES } from './templates.js';
|
|
47
|
+
import { CapsuleExporter } from './capsules.js';
|
|
48
|
+
import { FederationEngine } from './federation.js';
|
|
49
|
+
import { KnowledgePool } from './knowledge.js';
|
|
50
|
+
import { SkillsEngine } from './skills/engine.js';
|
|
51
|
+
import { SecurityManager } from './security.js';
|
|
52
|
+
import { IntegrationEngine } from './integration.js';
|
|
53
|
+
import { PersonaVaultEngine } from './persona/index.js';
|
|
54
|
+
import { ProactiveMissionEngine } from './proactive/engine.js';
|
|
55
|
+
import { spawn, execSync } from 'child_process';
|
|
56
|
+
import { fileURLToPath } from 'url';
|
|
57
|
+
import { resolve } from 'path';
|
|
58
|
+
import { realpathSync } from 'fs';
|
|
59
|
+
import { createClient } from './sdk/client.js';
|
|
60
|
+
import { registerNode, resolveNode, stopHeartbeat } from './registry.js';
|
|
61
|
+
// ─── Invite Code Helpers ─────────────────────────────────────────
|
|
62
|
+
// Encode a multiaddr + room into a short invite code: base64url
|
|
63
|
+
function encodeInvite(multiaddr, room) {
|
|
64
|
+
const payload = JSON.stringify({ a: multiaddr, r: room });
|
|
65
|
+
return Buffer.from(payload).toString('base64url');
|
|
66
|
+
}
|
|
67
|
+
function decodeInvite(code) {
|
|
68
|
+
try {
|
|
69
|
+
const payload = JSON.parse(Buffer.from(code, 'base64url').toString('utf-8'));
|
|
70
|
+
if (payload.a)
|
|
71
|
+
return { multiaddr: payload.a, room: payload.r || 'lobby' };
|
|
72
|
+
}
|
|
73
|
+
catch { /* not a valid invite code */ }
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const program = new Command();
|
|
77
|
+
program
|
|
78
|
+
.name('society')
|
|
79
|
+
.description('Society Protocol — Connect your AI agents')
|
|
80
|
+
.version('1.0.0');
|
|
81
|
+
// ─── DEFAULT ACTION (npx society) ────────────────────────────────
|
|
82
|
+
// Running `npx society` with no subcommand starts a node instantly
|
|
83
|
+
program
|
|
84
|
+
.argument('[name]', 'agent display name')
|
|
85
|
+
.option('-r, --room <room>', 'room to join', 'lobby')
|
|
86
|
+
.option('-b, --bootstrap <addrs...>', 'connect to remote network')
|
|
87
|
+
.option('-p, --port <port>', 'listen port', '0')
|
|
88
|
+
.option('--relay', 'expose as public relay (requires cloudflared)')
|
|
89
|
+
.option('--debug', 'enable debug logging')
|
|
90
|
+
.action(async (name, options) => {
|
|
91
|
+
if (name && !program.args.includes('node') && !program.args.includes('init') &&
|
|
92
|
+
!program.args.includes('join') && !program.args.includes('invite') &&
|
|
93
|
+
!program.args.includes('status') && !program.args.includes('dashboard') &&
|
|
94
|
+
!program.args.includes('mission') && !program.args.includes('swarm') &&
|
|
95
|
+
!program.args.includes('worker') && !program.args.includes('mcp')) {
|
|
96
|
+
await startNode({
|
|
97
|
+
name: name || 'Agent',
|
|
98
|
+
room: options.room,
|
|
99
|
+
port: options.port || '0',
|
|
100
|
+
bootstrap: options.bootstrap,
|
|
101
|
+
relay: options.relay,
|
|
102
|
+
gossipsub: true,
|
|
103
|
+
dht: true,
|
|
104
|
+
missionLeader: false,
|
|
105
|
+
provider: 'openai',
|
|
106
|
+
debug: options.debug,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
// ─── JOIN COMMAND ────────────────────────────────────────────────
|
|
111
|
+
// society join <invite-code-or-multiaddr>
|
|
112
|
+
program
|
|
113
|
+
.command('join <code>')
|
|
114
|
+
.description('Join a friend\'s network')
|
|
115
|
+
.option('-n, --name <name>', 'your agent name')
|
|
116
|
+
.option('-r, --room <room>', 'room to join')
|
|
117
|
+
.action(async (code, options) => {
|
|
118
|
+
const name = options.name || `Agent-${Math.random().toString(36).slice(2, 6)}`;
|
|
119
|
+
console.log('');
|
|
120
|
+
console.log(` ${bold('Society Protocol')} — Joining network...`);
|
|
121
|
+
console.log('');
|
|
122
|
+
let bootstrapAddr;
|
|
123
|
+
let room = options.room || 'lobby';
|
|
124
|
+
if (code.startsWith('/')) {
|
|
125
|
+
// Raw multiaddr
|
|
126
|
+
bootstrapAddr = code;
|
|
127
|
+
console.log(` Connecting to: ${dim(code.slice(0, 60))}...`);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
// Try invite code first
|
|
131
|
+
const decoded = decodeInvite(code);
|
|
132
|
+
if (decoded) {
|
|
133
|
+
bootstrapAddr = decoded.multiaddr;
|
|
134
|
+
if (!options.room)
|
|
135
|
+
room = decoded.room;
|
|
136
|
+
console.log(` Invite accepted! Joining room ${cyan(room)}...`);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
// Try resolving as a name from the registry
|
|
140
|
+
console.log(` Looking up ${bold(code)} in registry...`);
|
|
141
|
+
const resolved = await resolveNode(code);
|
|
142
|
+
if (resolved) {
|
|
143
|
+
bootstrapAddr = resolved.multiaddr;
|
|
144
|
+
if (!options.room)
|
|
145
|
+
room = resolved.room;
|
|
146
|
+
console.log(` Found ${bold(code)}! Joining room ${cyan(room)}...`);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
console.error(red(` Could not find "${code}".`));
|
|
150
|
+
console.log(` ${dim('Try an invite code or multiaddr instead.')}`);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
await startNode({
|
|
156
|
+
name,
|
|
157
|
+
room,
|
|
158
|
+
port: '0',
|
|
159
|
+
bootstrap: bootstrapAddr ? [bootstrapAddr] : undefined,
|
|
160
|
+
gossipsub: true,
|
|
161
|
+
dht: true,
|
|
162
|
+
missionLeader: false,
|
|
163
|
+
provider: 'openai',
|
|
164
|
+
debug: false,
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
// ─── INVITE COMMAND ─────────────────────────────────────────────
|
|
168
|
+
// society invite → generate a shareable invite code
|
|
169
|
+
program
|
|
170
|
+
.command('invite')
|
|
171
|
+
.description('Generate an invite for others to join your network')
|
|
172
|
+
.option('-n, --name <name>', 'register a friendly name (e.g. "alice")')
|
|
173
|
+
.option('-r, --room <room>', 'room to invite to', 'lobby')
|
|
174
|
+
.option('-p, --port <port>', 'P2P listen port', '4001')
|
|
175
|
+
.option('--relay', 'create a public relay so friends can join from anywhere')
|
|
176
|
+
.action(async (options) => {
|
|
177
|
+
console.log('');
|
|
178
|
+
console.log(` ${bold('Society Protocol')} — Generating invite...`);
|
|
179
|
+
console.log(` ${dim('Your node must stay running for others to connect.')}`);
|
|
180
|
+
console.log('');
|
|
181
|
+
const port = parseInt(options.port, 10);
|
|
182
|
+
const nodeName = options.name || 'Host';
|
|
183
|
+
const client = await createClient({
|
|
184
|
+
identity: { name: nodeName },
|
|
185
|
+
network: {
|
|
186
|
+
port,
|
|
187
|
+
enableGossipsub: true,
|
|
188
|
+
enableDht: true,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
await client.joinRoom(options.room);
|
|
192
|
+
const peerId = client.getPeerId();
|
|
193
|
+
// Local invite (LAN)
|
|
194
|
+
const localMultiaddr = `/ip4/127.0.0.1/tcp/${port}/p2p/${peerId}`;
|
|
195
|
+
const localCode = encodeInvite(localMultiaddr, options.room);
|
|
196
|
+
console.log(` ${green('Node running!')} Room: ${cyan(options.room)}`);
|
|
197
|
+
console.log('');
|
|
198
|
+
console.log(` ${bold('Share with friends on same network:')}`);
|
|
199
|
+
console.log('');
|
|
200
|
+
console.log(` ${bold(cyan(`npx society join ${localCode}`))}`);
|
|
201
|
+
console.log('');
|
|
202
|
+
// Register friendly name if provided
|
|
203
|
+
if (options.name) {
|
|
204
|
+
const registered = await registerNode(options.name, {
|
|
205
|
+
multiaddr: localMultiaddr,
|
|
206
|
+
room: options.room,
|
|
207
|
+
peerId,
|
|
208
|
+
name: options.name,
|
|
209
|
+
});
|
|
210
|
+
if (registered) {
|
|
211
|
+
console.log(` ${green('Registered!')} Friends can also join with:`);
|
|
212
|
+
console.log('');
|
|
213
|
+
console.log(` ${bold(cyan(`npx society join ${options.name}`))}`);
|
|
214
|
+
console.log('');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// If --relay, spawn cloudflared for a public URL
|
|
218
|
+
if (options.relay) {
|
|
219
|
+
const wsPort = port + 1;
|
|
220
|
+
console.log(` ${dim('Starting public relay...')}`);
|
|
221
|
+
if (!isCommandAvailable('cloudflared')) {
|
|
222
|
+
console.log(` ${yellow('cloudflared not found.')} Installing...`);
|
|
223
|
+
await installCloudflared();
|
|
224
|
+
}
|
|
225
|
+
if (isCommandAvailable('cloudflared')) {
|
|
226
|
+
const cfProc = spawn('cloudflared', ['tunnel', '--url', `http://localhost:${wsPort}`]);
|
|
227
|
+
cfProc.stderr?.on('data', (data) => {
|
|
228
|
+
const str = data.toString();
|
|
229
|
+
const match = str.match(/(https:\/\/[a-z0-9-]+\.trycloudflare\.com)/);
|
|
230
|
+
if (match) {
|
|
231
|
+
const host = match[1].replace('https://', '');
|
|
232
|
+
const publicMultiaddr = `/dns4/${host}/tcp/443/wss/p2p/${peerId}`;
|
|
233
|
+
const publicCode = encodeInvite(publicMultiaddr, options.room);
|
|
234
|
+
console.log(` ${bold(green('Public relay active!'))}`);
|
|
235
|
+
console.log('');
|
|
236
|
+
console.log(` ${bold('Share with anyone:')}`);
|
|
237
|
+
console.log('');
|
|
238
|
+
console.log(` ${bold(cyan(`npx society join ${publicCode}`))}`);
|
|
239
|
+
// Update registry with public multiaddr
|
|
240
|
+
if (options.name) {
|
|
241
|
+
registerNode(options.name, {
|
|
242
|
+
multiaddr: publicMultiaddr,
|
|
243
|
+
room: options.room,
|
|
244
|
+
peerId,
|
|
245
|
+
name: options.name,
|
|
246
|
+
}).then(ok => {
|
|
247
|
+
if (ok) {
|
|
248
|
+
console.log(` ${bold(cyan(`npx society join ${options.name}`))}`);
|
|
249
|
+
}
|
|
250
|
+
console.log('');
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
console.log('');
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
process.on('SIGINT', () => { cfProc.kill(); });
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
console.log(` ${yellow('Could not start relay.')} Share the local code above for LAN access.`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
console.log(` ${dim('For remote access, add')} ${cyan('--relay')} ${dim('to get a public invite code.')}`);
|
|
266
|
+
console.log('');
|
|
267
|
+
}
|
|
268
|
+
// Keep running
|
|
269
|
+
process.on('SIGINT', async () => {
|
|
270
|
+
stopHeartbeat();
|
|
271
|
+
await client.disconnect();
|
|
272
|
+
process.exit(0);
|
|
273
|
+
});
|
|
274
|
+
process.stdin.resume();
|
|
275
|
+
});
|
|
276
|
+
// ─── STATUS COMMAND ─────────────────────────────────────────────
|
|
277
|
+
// society status → show current state
|
|
278
|
+
program
|
|
279
|
+
.command('status')
|
|
280
|
+
.description('Show the status of your Society node')
|
|
281
|
+
.option('--db <path>', 'SQLite database path')
|
|
282
|
+
.action(async (options) => {
|
|
283
|
+
const storage = new Storage(options.db ? { dbPath: options.db } : undefined);
|
|
284
|
+
console.log('');
|
|
285
|
+
console.log(` ${bold('Society Protocol')} — Status`);
|
|
286
|
+
console.log('');
|
|
287
|
+
const identity = storage.getIdentity();
|
|
288
|
+
if (identity) {
|
|
289
|
+
console.log(` Identity: ${bold(identity.display_name)} (${identity.did.slice(0, 24)}...)`);
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
console.log(` Identity: ${dim('Not initialized yet. Run:')} ${cyan('npx society')}`);
|
|
293
|
+
}
|
|
294
|
+
const rooms = storage.query('SELECT DISTINCT room_id FROM rooms');
|
|
295
|
+
if (rooms.length > 0) {
|
|
296
|
+
console.log(` Rooms: ${rooms.map((r) => r.room_id).join(', ')}`);
|
|
297
|
+
}
|
|
298
|
+
const chains = storage.query('SELECT COUNT(*) as count FROM chains');
|
|
299
|
+
console.log(` Chains: ${chains[0]?.count || 0} total`);
|
|
300
|
+
const steps = storage.query("SELECT COUNT(*) as count FROM steps WHERE status = 'completed'");
|
|
301
|
+
console.log(` Steps: ${steps[0]?.count || 0} completed`);
|
|
302
|
+
console.log('');
|
|
303
|
+
storage.close();
|
|
304
|
+
});
|
|
305
|
+
// ─── MCP COMMAND ─────────────────────────────────────────────────
|
|
306
|
+
program
|
|
307
|
+
.command('mcp')
|
|
308
|
+
.description('Start MCP server for Claude, Cursor, Windsurf')
|
|
309
|
+
.option('-n, --name <name>', 'agent name', process.env.SOCIETY_IDENTITY_NAME || 'MCP-Agent')
|
|
310
|
+
.option('-r, --room <room>', 'default room', 'lobby')
|
|
311
|
+
.option('-b, --bootstrap <addrs...>', 'bootstrap multiaddrs')
|
|
312
|
+
.action(async (options) => {
|
|
313
|
+
const client = await createClient({
|
|
314
|
+
identity: { name: options.name },
|
|
315
|
+
network: {
|
|
316
|
+
bootstrap: options.bootstrap,
|
|
317
|
+
enableGossipsub: true,
|
|
318
|
+
enableDht: true,
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
await client.joinRoom(options.room);
|
|
322
|
+
const { SocietyMCPServer } = await import('./mcp/server.js');
|
|
323
|
+
const server = new SocietyMCPServer({ client });
|
|
324
|
+
await server.run();
|
|
325
|
+
});
|
|
326
|
+
// ─── NODE COMMAND (power user) ──────────────────────────────────
|
|
327
|
+
program
|
|
328
|
+
.command('node')
|
|
329
|
+
.description('Start a Society node (advanced options)')
|
|
330
|
+
.option('-n, --name <name>', 'display name', 'Agent')
|
|
331
|
+
.option('-r, --room <room>', 'room ID to join', 'lobby')
|
|
332
|
+
.option('-p, --port <port>', 'listen port (0 = random)', '0')
|
|
333
|
+
.option('-b, --bootstrap <addrs...>', 'bootstrap multiaddrs')
|
|
334
|
+
.option('--db <path>', 'SQLite database path')
|
|
335
|
+
.option('--relay', 'spawn cloudflared to create a public WebSocket relay')
|
|
336
|
+
.option('--gossipsub', 'enable GossipSub for scalable pub/sub', true)
|
|
337
|
+
.option('--dht', 'enable DHT for peer discovery', true)
|
|
338
|
+
.option('--mission-leader', 'enable proactive mission leadership with auto-restore', false)
|
|
339
|
+
.option('--provider <provider>', 'AI planner provider (openai|anthropic|ollama)', 'openai')
|
|
340
|
+
.option('--debug', 'enable debug logging')
|
|
341
|
+
.action(async (options) => {
|
|
342
|
+
await startNode(options);
|
|
343
|
+
});
|
|
344
|
+
// ─── INIT COMMAND ─────────────────────────────────────────────────
|
|
345
|
+
import { AutoConfigurator, detectCI, detectContainer } from './autoconfig.js';
|
|
346
|
+
import { BootstrapManager } from './bootstrap.js';
|
|
347
|
+
program
|
|
348
|
+
.command('init')
|
|
349
|
+
.description('Interactive setup wizard for Society Protocol')
|
|
350
|
+
.option('--quick', 'Quick setup with defaults')
|
|
351
|
+
.option('--name <name>', 'Agent name')
|
|
352
|
+
.option('--room <room>', 'Default room to join')
|
|
353
|
+
.option('--template <name>', 'Default template for /summon')
|
|
354
|
+
.action(async (options) => {
|
|
355
|
+
await runInitWizard(options);
|
|
356
|
+
});
|
|
357
|
+
program
|
|
358
|
+
.command('dashboard')
|
|
359
|
+
.description('Launch the Society Dashboard — visual mission control')
|
|
360
|
+
.option('-p, --port <port>', 'Dashboard server port', '4200')
|
|
361
|
+
.option('-n, --name <name>', 'Agent display name', 'Dashboard')
|
|
362
|
+
.option('-r, --room <room>', 'Initial room to join', 'lobby')
|
|
363
|
+
.option('-b, --bootstrap <addrs...>', 'Bootstrap peer addresses')
|
|
364
|
+
.option('--connect <url>', 'Connect to existing Society node (remote mode)')
|
|
365
|
+
.option('--p2p-port <port>', 'P2P listening port')
|
|
366
|
+
.action(async (opts) => {
|
|
367
|
+
try {
|
|
368
|
+
const dashboardPath = resolve(realpathSync(fileURLToPath(import.meta.url)), '../../../dashboard/src/server/index.ts');
|
|
369
|
+
const args = ['tsx', dashboardPath, '--port', opts.port, '--name', opts.name, '--room', opts.room];
|
|
370
|
+
if (opts.bootstrap)
|
|
371
|
+
opts.bootstrap.forEach((b) => args.push('--bootstrap', b));
|
|
372
|
+
if (opts.connect)
|
|
373
|
+
args.push('--connect', opts.connect);
|
|
374
|
+
if (opts.p2pPort)
|
|
375
|
+
args.push('--p2p-port', opts.p2pPort);
|
|
376
|
+
const child = spawn('npx', args, { stdio: 'inherit', cwd: resolve(realpathSync(fileURLToPath(import.meta.url)), '../../../dashboard') });
|
|
377
|
+
child.on('error', (err) => { console.error('Failed to start dashboard:', err.message); process.exit(1); });
|
|
378
|
+
child.on('exit', (code) => process.exit(code || 0));
|
|
379
|
+
}
|
|
380
|
+
catch (err) {
|
|
381
|
+
console.error('Dashboard not found. Run from the society repo root or install society-dashboard.');
|
|
382
|
+
console.error(err.message);
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
const mission = program.command('mission').description('Manage proactive research missions');
|
|
387
|
+
mission
|
|
388
|
+
.command('start')
|
|
389
|
+
.requiredOption('--room <room>', 'room ID')
|
|
390
|
+
.requiredOption('--goal <goal>', 'research goal')
|
|
391
|
+
.option('--template <template>', 'mission template', 'literature_review_continuous')
|
|
392
|
+
.option('--cadence-ms <ms>', 'mission cadence in milliseconds', '300000')
|
|
393
|
+
.option('--detach', 'start mission and exit immediately instead of running leader loop', false)
|
|
394
|
+
.option('--name <name>', 'identity/display name', 'Mission Leader')
|
|
395
|
+
.option('--db <path>', 'SQLite database path')
|
|
396
|
+
.option('--bootstrap <addrs...>', 'bootstrap multiaddrs')
|
|
397
|
+
.action(async (options) => {
|
|
398
|
+
const client = await createClient({
|
|
399
|
+
identity: { name: options.name },
|
|
400
|
+
storage: options.db ? { path: options.db } : undefined,
|
|
401
|
+
network: {
|
|
402
|
+
bootstrap: options.bootstrap,
|
|
403
|
+
enableGossipsub: true,
|
|
404
|
+
enableDht: true,
|
|
405
|
+
},
|
|
406
|
+
proactive: {
|
|
407
|
+
enableLeadership: true,
|
|
408
|
+
autoRestoreMissions: true,
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
try {
|
|
412
|
+
await client.joinRoom(options.room);
|
|
413
|
+
const missionInfo = await client.startMission({
|
|
414
|
+
roomId: options.room,
|
|
415
|
+
goal: options.goal,
|
|
416
|
+
missionType: 'scientific_research',
|
|
417
|
+
templateId: options.template,
|
|
418
|
+
mode: 'continuous',
|
|
419
|
+
cadenceMs: parseInt(options.cadenceMs, 10),
|
|
420
|
+
policy: {
|
|
421
|
+
autonomy: 'semiautonomous',
|
|
422
|
+
approvalGates: ['publish', 'external_write', 'costly_action'],
|
|
423
|
+
swarm: {
|
|
424
|
+
minWorkers: 2,
|
|
425
|
+
maxWorkers: 12,
|
|
426
|
+
targetUtilization: 0.7,
|
|
427
|
+
leaseMs: 120000,
|
|
428
|
+
rebalanceIntervalMs: 30000,
|
|
429
|
+
},
|
|
430
|
+
retry: {
|
|
431
|
+
maxStepRetries: 3,
|
|
432
|
+
maxMissionReplans: 20,
|
|
433
|
+
cooldownMs: 60000,
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
research: {
|
|
437
|
+
sources: ['arxiv', 'pubmed', 'crossref', 'semantic-scholar', 'web'],
|
|
438
|
+
subdomainsPerCycle: 4,
|
|
439
|
+
requireDualReview: true,
|
|
440
|
+
requireCitationExtraction: true,
|
|
441
|
+
requireContradictionScan: true,
|
|
442
|
+
synthesisIntervalMs: parseInt(options.cadenceMs, 10),
|
|
443
|
+
},
|
|
444
|
+
knowledge: { autoIndex: true },
|
|
445
|
+
});
|
|
446
|
+
console.log(JSON.stringify(missionInfo, null, 2));
|
|
447
|
+
if (!options.detach) {
|
|
448
|
+
console.log(`[mission] leader active for mission ${missionInfo.missionId}. Press Ctrl+C to stop.`);
|
|
449
|
+
await new Promise((resolve) => {
|
|
450
|
+
const done = () => resolve();
|
|
451
|
+
process.once('SIGINT', done);
|
|
452
|
+
process.once('SIGTERM', done);
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
finally {
|
|
457
|
+
await client.disconnect();
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
mission
|
|
461
|
+
.command('list')
|
|
462
|
+
.option('--room <room>', 'optional room filter')
|
|
463
|
+
.option('--name <name>', 'identity/display name', 'Mission Leader')
|
|
464
|
+
.option('--db <path>', 'SQLite database path')
|
|
465
|
+
.option('--bootstrap <addrs...>', 'bootstrap multiaddrs')
|
|
466
|
+
.action(async (options) => {
|
|
467
|
+
const client = await createClient({
|
|
468
|
+
identity: { name: options.name },
|
|
469
|
+
storage: options.db ? { path: options.db } : undefined,
|
|
470
|
+
network: {
|
|
471
|
+
bootstrap: options.bootstrap,
|
|
472
|
+
enableGossipsub: true,
|
|
473
|
+
enableDht: true,
|
|
474
|
+
},
|
|
475
|
+
});
|
|
476
|
+
try {
|
|
477
|
+
console.log(JSON.stringify(await client.listMissions(options.room), null, 2));
|
|
478
|
+
}
|
|
479
|
+
finally {
|
|
480
|
+
await client.disconnect();
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
for (const action of ['pause', 'resume', 'stop']) {
|
|
484
|
+
mission
|
|
485
|
+
.command(action)
|
|
486
|
+
.requiredOption('--mission-id <id>', 'mission id')
|
|
487
|
+
.option('--reason <reason>', 'stop reason')
|
|
488
|
+
.option('--name <name>', 'identity/display name', 'Mission Leader')
|
|
489
|
+
.option('--db <path>', 'SQLite database path')
|
|
490
|
+
.option('--bootstrap <addrs...>', 'bootstrap multiaddrs')
|
|
491
|
+
.action(async (options) => {
|
|
492
|
+
const client = await createClient({
|
|
493
|
+
identity: { name: options.name },
|
|
494
|
+
storage: options.db ? { path: options.db } : undefined,
|
|
495
|
+
network: {
|
|
496
|
+
bootstrap: options.bootstrap,
|
|
497
|
+
enableGossipsub: true,
|
|
498
|
+
enableDht: true,
|
|
499
|
+
},
|
|
500
|
+
});
|
|
501
|
+
try {
|
|
502
|
+
if (action === 'pause')
|
|
503
|
+
await client.pauseMission(options.missionId);
|
|
504
|
+
if (action === 'resume')
|
|
505
|
+
await client.resumeMission(options.missionId);
|
|
506
|
+
if (action === 'stop')
|
|
507
|
+
await client.stopMission(options.missionId, options.reason);
|
|
508
|
+
console.log(`${action}d ${options.missionId}`);
|
|
509
|
+
}
|
|
510
|
+
finally {
|
|
511
|
+
await client.disconnect();
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
const swarm = program.command('swarm').description('Inspect swarm worker status');
|
|
516
|
+
swarm
|
|
517
|
+
.command('status')
|
|
518
|
+
.option('--room <room>', 'optional room filter')
|
|
519
|
+
.option('--name <name>', 'identity/display name', 'Mission Leader')
|
|
520
|
+
.option('--db <path>', 'SQLite database path')
|
|
521
|
+
.option('--bootstrap <addrs...>', 'bootstrap multiaddrs')
|
|
522
|
+
.action(async (options) => {
|
|
523
|
+
const client = await createClient({
|
|
524
|
+
identity: { name: options.name },
|
|
525
|
+
storage: options.db ? { path: options.db } : undefined,
|
|
526
|
+
network: {
|
|
527
|
+
bootstrap: options.bootstrap,
|
|
528
|
+
enableGossipsub: true,
|
|
529
|
+
enableDht: true,
|
|
530
|
+
},
|
|
531
|
+
});
|
|
532
|
+
try {
|
|
533
|
+
console.log(JSON.stringify(await client.getSwarmStatus(options.room), null, 2));
|
|
534
|
+
}
|
|
535
|
+
finally {
|
|
536
|
+
await client.disconnect();
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
const worker = program.command('worker').description('Run specialized Society workers');
|
|
540
|
+
worker
|
|
541
|
+
.command('research')
|
|
542
|
+
.requiredOption('--room <room>', 'mission room')
|
|
543
|
+
.requiredOption('--host-id <id>', 'worker host identifier')
|
|
544
|
+
.option('--runtime <runtime>', 'runtime type', 'nanobot')
|
|
545
|
+
.option('--specialties <items...>', 'worker specialties')
|
|
546
|
+
.option('--capabilities <items...>', 'worker capabilities')
|
|
547
|
+
.option('--name <name>', 'identity/display name', 'Research Worker')
|
|
548
|
+
.option('--db <path>', 'SQLite database path')
|
|
549
|
+
.option('--bootstrap <addrs...>', 'bootstrap multiaddrs')
|
|
550
|
+
.action(async (options) => {
|
|
551
|
+
const client = await createClient({
|
|
552
|
+
identity: { name: options.name },
|
|
553
|
+
storage: options.db ? { path: options.db } : undefined,
|
|
554
|
+
network: {
|
|
555
|
+
bootstrap: options.bootstrap,
|
|
556
|
+
enableGossipsub: true,
|
|
557
|
+
enableDht: true,
|
|
558
|
+
},
|
|
559
|
+
});
|
|
560
|
+
await client.startResearchWorker({
|
|
561
|
+
roomId: options.room,
|
|
562
|
+
hostId: options.hostId,
|
|
563
|
+
runtime: options.runtime,
|
|
564
|
+
specialties: options.specialties || ['research'],
|
|
565
|
+
capabilities: options.capabilities || ['research', 'academic-search', 'evidence-extraction'],
|
|
566
|
+
});
|
|
567
|
+
console.log(`[worker] research worker active in room ${options.room}`);
|
|
568
|
+
process.on('SIGINT', () => {
|
|
569
|
+
client.disconnect().finally(() => process.exit(0));
|
|
570
|
+
});
|
|
571
|
+
process.stdin.resume();
|
|
572
|
+
});
|
|
573
|
+
async function runInitWizard(options) {
|
|
574
|
+
const rl = readline.createInterface({
|
|
575
|
+
input: process.stdin,
|
|
576
|
+
output: process.stdout
|
|
577
|
+
});
|
|
578
|
+
const question = (prompt) => {
|
|
579
|
+
return new Promise((resolve) => {
|
|
580
|
+
rl.question(prompt, (answer) => resolve(answer.trim()));
|
|
581
|
+
});
|
|
582
|
+
};
|
|
583
|
+
const choose = async (prompt, choices) => {
|
|
584
|
+
console.log(`\n${prompt}`);
|
|
585
|
+
choices.forEach((c, i) => console.log(` ${i + 1}. ${c}`));
|
|
586
|
+
const answer = await question('Select (number): ');
|
|
587
|
+
const idx = parseInt(answer) - 1;
|
|
588
|
+
return choices[idx] || choices[0];
|
|
589
|
+
};
|
|
590
|
+
const multichoose = async (prompt, choices) => {
|
|
591
|
+
console.log(`\n${prompt}`);
|
|
592
|
+
console.log(' (Space-separated numbers, e.g., "1 3")');
|
|
593
|
+
choices.forEach((c, i) => console.log(` ${i + 1}. ${c}`));
|
|
594
|
+
const answer = await question('Select: ');
|
|
595
|
+
const indices = answer.split(/\s+/).map(n => parseInt(n) - 1).filter(n => !isNaN(n) && n >= 0 && n < choices.length);
|
|
596
|
+
return indices.map(i => choices[i]);
|
|
597
|
+
};
|
|
598
|
+
console.log('');
|
|
599
|
+
console.log(' ╔══════════════════════════════════════════════════════════╗');
|
|
600
|
+
console.log(' ║ 🚀 Society Protocol Setup Wizard ║');
|
|
601
|
+
console.log(' ║ Let\'s get you connected! ║');
|
|
602
|
+
console.log(' ╚══════════════════════════════════════════════════════════╝');
|
|
603
|
+
console.log('');
|
|
604
|
+
// Check if CI/container
|
|
605
|
+
const ciInfo = detectCI();
|
|
606
|
+
const isContainer = detectContainer();
|
|
607
|
+
if (ciInfo.isCI) {
|
|
608
|
+
console.log(` ℹ️ Detected CI environment: ${ciInfo.provider}`);
|
|
609
|
+
}
|
|
610
|
+
if (isContainer) {
|
|
611
|
+
console.log(' ℹ️ Detected container environment');
|
|
612
|
+
}
|
|
613
|
+
// Step 1: Auto-detect system
|
|
614
|
+
console.log('\n📊 Analyzing your system...');
|
|
615
|
+
const autoConfig = new AutoConfigurator();
|
|
616
|
+
const config = await autoConfig.generateConfig();
|
|
617
|
+
console.log(` Environment: ${config.environment.type}`);
|
|
618
|
+
console.log(` CPU: ${config.resources.cpu.cores} cores`);
|
|
619
|
+
console.log(` Memory: ${(config.resources.memory.total / (1024 ** 3)).toFixed(1)} GB`);
|
|
620
|
+
console.log(` Recommended mode: ${bold(config.usage.type.toUpperCase())}`);
|
|
621
|
+
if (options.quick) {
|
|
622
|
+
console.log('\n⚡ Quick mode: Using auto-detected settings...\n');
|
|
623
|
+
await autoConfig.applyConfig(config);
|
|
624
|
+
rl.close();
|
|
625
|
+
console.log(green('✅ Setup complete!'));
|
|
626
|
+
console.log(`\nNext steps:`);
|
|
627
|
+
console.log(` 1. Start the node: ${cyan('society node')}`);
|
|
628
|
+
console.log(` 2. Or customize: ${cyan('society init')} (without --quick)\n`);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
// Step 2: What are you building?
|
|
632
|
+
const useCase = await choose('What do you want to build?', [
|
|
633
|
+
'Personal AI Assistant - Single agent for personal tasks',
|
|
634
|
+
'Dev Team Collaboration - Code reviews, PR automation',
|
|
635
|
+
'Research Group - Multi-agent research coordination',
|
|
636
|
+
'Content Creation - Writing, editing, publishing pipeline',
|
|
637
|
+
'Custom Setup - I\'ll configure everything manually'
|
|
638
|
+
]);
|
|
639
|
+
// Step 3: AI Providers
|
|
640
|
+
const providers = await multichoose('Which AI providers do you have access to?', [
|
|
641
|
+
'OpenAI (GPT-4o)',
|
|
642
|
+
'Anthropic (Claude)',
|
|
643
|
+
'Ollama (local models)',
|
|
644
|
+
'Custom/OpenRouter'
|
|
645
|
+
]);
|
|
646
|
+
const primaryProvider = providers[0]?.toLowerCase().split(' ')[0] || 'openai';
|
|
647
|
+
// Step 4: Agent identity
|
|
648
|
+
const name = options.name || await question(`\nChoose your agent name (default: Agent): `) || 'Agent';
|
|
649
|
+
// Step 5: Room setup
|
|
650
|
+
const roomOption = await choose('\nRoom setup:', [
|
|
651
|
+
'Create a new private room',
|
|
652
|
+
'Join existing room by ID',
|
|
653
|
+
'Start in public lobby'
|
|
654
|
+
]);
|
|
655
|
+
let roomId;
|
|
656
|
+
if (roomOption.includes('new')) {
|
|
657
|
+
const roomName = await question('Room name (no spaces): ') || `${name.toLowerCase()}-room`;
|
|
658
|
+
roomId = roomName.replace(/\s+/g, '-');
|
|
659
|
+
}
|
|
660
|
+
else if (roomOption.includes('existing')) {
|
|
661
|
+
roomId = await question('Enter room ID: ') || 'lobby';
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
664
|
+
roomId = 'lobby';
|
|
665
|
+
}
|
|
666
|
+
// Step 6: Template preference
|
|
667
|
+
const templateNames = Object.keys(TEMPLATES);
|
|
668
|
+
let defaultTemplate = options.template;
|
|
669
|
+
if (!defaultTemplate && templateNames.length > 0) {
|
|
670
|
+
const templateChoice = await choose('\nDefault template for quick tasks:', [
|
|
671
|
+
'None - I\'ll specify each time',
|
|
672
|
+
...templateNames.map(t => `${t} - ${TEMPLATES[t]?.description || 'Custom template'}`)
|
|
673
|
+
]);
|
|
674
|
+
if (!templateChoice.includes('None')) {
|
|
675
|
+
defaultTemplate = templateChoice.split(' ')[0];
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
// Step 7: Security preferences
|
|
679
|
+
console.log('\n🔒 Security Configuration:');
|
|
680
|
+
const requireAuth = await choose('Require authentication for adapter API?', [
|
|
681
|
+
'Yes - Generate API key (recommended)',
|
|
682
|
+
'No - Allow local connections only'
|
|
683
|
+
]);
|
|
684
|
+
const apiKey = requireAuth.includes('Yes')
|
|
685
|
+
? Buffer.from(crypto.randomUUID()).toString('base64').slice(0, 32)
|
|
686
|
+
: undefined;
|
|
687
|
+
// Step 8: Apply configuration
|
|
688
|
+
console.log('\n⚙️ Applying configuration...');
|
|
689
|
+
// Save identity preference
|
|
690
|
+
const storage = new Storage();
|
|
691
|
+
const identity = generateIdentity(name);
|
|
692
|
+
const privHex = uint8ToString(identity.privateKey, 'base16');
|
|
693
|
+
const pubHex = uint8ToString(identity.publicKey, 'base16');
|
|
694
|
+
storage.saveIdentity(identity.did, privHex, pubHex, name);
|
|
695
|
+
// Generate config file
|
|
696
|
+
const { writeFileSync, mkdirSync } = await import('fs');
|
|
697
|
+
const { join } = await import('path');
|
|
698
|
+
const { homedir } = await import('os');
|
|
699
|
+
const configDir = join(homedir(), '.society');
|
|
700
|
+
mkdirSync(configDir, { recursive: true });
|
|
701
|
+
const userConfig = `
|
|
702
|
+
# Society Protocol - User Configuration
|
|
703
|
+
# Generated: ${new Date().toISOString()}
|
|
704
|
+
|
|
705
|
+
identity:
|
|
706
|
+
name: "${name}"
|
|
707
|
+
did: "${identity.did}"
|
|
708
|
+
|
|
709
|
+
room:
|
|
710
|
+
default: "${roomId}"
|
|
711
|
+
|
|
712
|
+
ai:
|
|
713
|
+
providers:
|
|
714
|
+
${providers.map(p => ` - ${p.toLowerCase().split(' ')[0]}`).join('\n')}
|
|
715
|
+
primary: "${primaryProvider}"
|
|
716
|
+
|
|
717
|
+
${defaultTemplate ? `template:\n default: "${defaultTemplate}"` : ''}
|
|
718
|
+
|
|
719
|
+
adapter:
|
|
720
|
+
host: "127.0.0.1"
|
|
721
|
+
port: ${config.recommended.apiPort}
|
|
722
|
+
${apiKey ? `auth:\n type: bearer\n api_key: "${apiKey}"` : 'auth:\n type: none'}
|
|
723
|
+
|
|
724
|
+
network:
|
|
725
|
+
mode: "${config.usage.type}"
|
|
726
|
+
max_peers: ${config.recommended.maxPeers}
|
|
727
|
+
bootstrap:
|
|
728
|
+
dns_discovery: true
|
|
729
|
+
fallback_peers: true
|
|
730
|
+
`;
|
|
731
|
+
writeFileSync(join(configDir, 'config.yml'), userConfig.trim());
|
|
732
|
+
await autoConfig.applyConfig(config);
|
|
733
|
+
// Summary
|
|
734
|
+
console.log('');
|
|
735
|
+
console.log(green('✅ Setup complete!'));
|
|
736
|
+
console.log('');
|
|
737
|
+
console.log('📋 Configuration Summary:');
|
|
738
|
+
console.log(` Identity: ${cyan(name)} (${identity.did.slice(0, 20)}...)`);
|
|
739
|
+
console.log(` Room: ${cyan(roomId)}`);
|
|
740
|
+
console.log(` AI Provider: ${cyan(primaryProvider)}`);
|
|
741
|
+
console.log(` API Port: ${cyan(config.recommended.apiPort.toString())}`);
|
|
742
|
+
if (apiKey) {
|
|
743
|
+
console.log(` API Key: ${cyan(apiKey)}`);
|
|
744
|
+
}
|
|
745
|
+
console.log('');
|
|
746
|
+
console.log('🚀 Next steps:');
|
|
747
|
+
console.log(` 1. Start your node: ${cyan('society node')}`);
|
|
748
|
+
console.log(` 2. Or with options: ${cyan(`society node --name "${name}" --room ${roomId}`)}`);
|
|
749
|
+
console.log(` 3. Start collaborating: ${cyan('/summon "Your task here"')}`);
|
|
750
|
+
console.log('');
|
|
751
|
+
console.log(`📚 Documentation: ${cyan('https://docs.society.dev')}`);
|
|
752
|
+
console.log(`💬 Community: ${cyan('https://discord.gg/society')}`);
|
|
753
|
+
console.log('');
|
|
754
|
+
rl.close();
|
|
755
|
+
}
|
|
756
|
+
const isMainModule = (() => {
|
|
757
|
+
const toRealPath = (candidate) => {
|
|
758
|
+
try {
|
|
759
|
+
return realpathSync(candidate);
|
|
760
|
+
}
|
|
761
|
+
catch {
|
|
762
|
+
return resolve(candidate);
|
|
763
|
+
}
|
|
764
|
+
};
|
|
765
|
+
const entry = process.argv[1] ? toRealPath(process.argv[1]) : '';
|
|
766
|
+
const current = toRealPath(fileURLToPath(import.meta.url));
|
|
767
|
+
return entry === current;
|
|
768
|
+
})();
|
|
769
|
+
if (isMainModule) {
|
|
770
|
+
program.parse();
|
|
771
|
+
}
|
|
772
|
+
async function startNode(options) {
|
|
773
|
+
const DEBUG = options.debug || process.env.SOCIETY_DEBUG === 'true';
|
|
774
|
+
// Banner
|
|
775
|
+
console.log('');
|
|
776
|
+
console.log(' ╔══════════════════════════════════════════════════════════╗');
|
|
777
|
+
console.log(' ║ 🌐 Society Protocol v1.0 (State of Art) ║');
|
|
778
|
+
console.log(' ║ P2P Multi-Agent Collaboration Network Node ║');
|
|
779
|
+
console.log(' ╠══════════════════════════════════════════════════════════╣');
|
|
780
|
+
console.log(' ║ Features: GossipSub • DHT • Reputation • Multi-Provider ║');
|
|
781
|
+
console.log(' ╚══════════════════════════════════════════════════════════╝');
|
|
782
|
+
console.log('');
|
|
783
|
+
// 1. Initialize storage
|
|
784
|
+
const storage = new Storage(options.db ? { dbPath: options.db } : undefined);
|
|
785
|
+
console.log('[init] Storage initialized.');
|
|
786
|
+
// 2. Initialize or restore identity
|
|
787
|
+
let identity;
|
|
788
|
+
const existingIdentity = storage.getIdentity();
|
|
789
|
+
if (existingIdentity) {
|
|
790
|
+
identity = restoreIdentity(existingIdentity.private_key_hex, options.name);
|
|
791
|
+
console.log(`[init] Identity restored: ${identity.did.slice(0, 32)}...`);
|
|
792
|
+
}
|
|
793
|
+
else {
|
|
794
|
+
identity = generateIdentity(options.name);
|
|
795
|
+
const privHex = uint8ToString(identity.privateKey, 'base16');
|
|
796
|
+
const pubHex = uint8ToString(identity.publicKey, 'base16');
|
|
797
|
+
storage.saveIdentity(identity.did, privHex, pubHex, identity.displayName);
|
|
798
|
+
console.log(`[init] New identity generated: ${identity.did.slice(0, 32)}...`);
|
|
799
|
+
}
|
|
800
|
+
console.log(`[init] Display name: ${bold(identity.displayName)}`);
|
|
801
|
+
// 3. Initialize reputation engine
|
|
802
|
+
const reputation = new ReputationEngine(storage);
|
|
803
|
+
console.log('[init] Reputation engine ready.');
|
|
804
|
+
// 4. Discover bootstrap peers when --bootstrap is not provided
|
|
805
|
+
let bootstrapAddrs = options.bootstrap;
|
|
806
|
+
if (!bootstrapAddrs?.length) {
|
|
807
|
+
try {
|
|
808
|
+
const bootstrapManager = new BootstrapManager();
|
|
809
|
+
const peers = await bootstrapManager.discover();
|
|
810
|
+
bootstrapAddrs = peers.flatMap((peer) => peer.addrs);
|
|
811
|
+
if (bootstrapAddrs.length > 0) {
|
|
812
|
+
console.log(`[bootstrap] Discovered ${bootstrapAddrs.length} bootstrap addresses`);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
catch (err) {
|
|
816
|
+
console.warn(`[bootstrap] Discovery unavailable: ${err?.message || 'unknown error'}`);
|
|
817
|
+
console.warn('[bootstrap] Continuing with local discovery only (mDNS/DHT).');
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
// 5. Start P2P node
|
|
821
|
+
const p2p = new P2PNode();
|
|
822
|
+
await p2p.start({
|
|
823
|
+
port: parseInt(options.port, 10),
|
|
824
|
+
bootstrapAddrs,
|
|
825
|
+
enableMdns: true,
|
|
826
|
+
enableDht: options.dht,
|
|
827
|
+
enableGossipsub: options.gossipsub,
|
|
828
|
+
});
|
|
829
|
+
// 6. Initialize room manager
|
|
830
|
+
const rooms = new RoomManager(identity, p2p, storage);
|
|
831
|
+
// 7. Join room
|
|
832
|
+
const roomId = options.room;
|
|
833
|
+
await rooms.joinRoom(roomId, roomId);
|
|
834
|
+
console.log(`[init] Joined room: ${roomId}`);
|
|
835
|
+
// 8. Initialize CoC Engine with reputation
|
|
836
|
+
const coc = new CocEngine(identity, rooms, storage, reputation);
|
|
837
|
+
// 9. Initialize Planner with multi-provider support
|
|
838
|
+
const planner = new Planner({
|
|
839
|
+
provider: options.provider,
|
|
840
|
+
enableCache: true,
|
|
841
|
+
fallbackChain: ['openai', 'anthropic', 'ollama'],
|
|
842
|
+
});
|
|
843
|
+
// 10. Initialize Adapter Host (localhost-only with optional API key)
|
|
844
|
+
const adapterPort = parseInt(process.env.SOCIETY_ADAPTER_PORT || '8080', 10);
|
|
845
|
+
const adapterHost = new AdapterHost(storage, coc, {
|
|
846
|
+
port: adapterPort,
|
|
847
|
+
host: '127.0.0.1', // Localhost only for security
|
|
848
|
+
security: {
|
|
849
|
+
apiKey: process.env.SOCIETY_API_KEY, // Optional: require API key
|
|
850
|
+
rateLimitEnabled: true,
|
|
851
|
+
rateLimitMaxRequests: 1000,
|
|
852
|
+
securityHeaders: true,
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
adapterHost.start();
|
|
856
|
+
console.log(`[init] Adapter Host listening on 127.0.0.1:${adapterPort}`);
|
|
857
|
+
if (process.env.SOCIETY_API_KEY) {
|
|
858
|
+
console.log('[init] API Key authentication enabled');
|
|
859
|
+
}
|
|
860
|
+
else {
|
|
861
|
+
console.log('[init] WARNING: No API key set. Set SOCIETY_API_KEY for production.');
|
|
862
|
+
}
|
|
863
|
+
// 11. Initialize Capsule Exporter
|
|
864
|
+
const exporter = new CapsuleExporter(coc, storage);
|
|
865
|
+
// 12. Initialize federation/integration stack (Federation Mesh)
|
|
866
|
+
const federation = new FederationEngine(storage, identity);
|
|
867
|
+
const knowledge = new KnowledgePool(storage, identity);
|
|
868
|
+
const skills = new SkillsEngine(storage, identity);
|
|
869
|
+
const security = new SecurityManager(identity);
|
|
870
|
+
const persona = new PersonaVaultEngine(storage, identity.did, {
|
|
871
|
+
defaultVaultName: `${identity.displayName} Persona Vault`,
|
|
872
|
+
});
|
|
873
|
+
const integration = new IntegrationEngine(storage, identity, federation, rooms, knowledge, coc, skills, security);
|
|
874
|
+
integration.attachPersonaVault(persona);
|
|
875
|
+
const proactiveLeader = options.missionLeader
|
|
876
|
+
? new ProactiveMissionEngine(identity, storage, rooms, coc, planner, knowledge, undefined, undefined, {
|
|
877
|
+
enableLeadership: true,
|
|
878
|
+
autoRestoreMissions: true,
|
|
879
|
+
})
|
|
880
|
+
: undefined;
|
|
881
|
+
if (proactiveLeader) {
|
|
882
|
+
console.log('[init] Mission leader mode enabled (auto-restore active).');
|
|
883
|
+
}
|
|
884
|
+
console.log('');
|
|
885
|
+
console.log(' Type a message and press Enter to send.');
|
|
886
|
+
console.log(' Commands: /peers /rooms /presence /reputation /info /history');
|
|
887
|
+
console.log(' /summon /template /chains /step /export /quit');
|
|
888
|
+
console.log(' /mesh-request /mesh-peerings /mesh-open /mesh-bridges /mesh-stats');
|
|
889
|
+
console.log(' ──────────────────────────────────────────────────────────────');
|
|
890
|
+
console.log('');
|
|
891
|
+
// ─── Event Listeners ────────────────────────────────────────
|
|
892
|
+
rooms.on('chat:message', (_roomId, envelope) => {
|
|
893
|
+
const body = envelope.body;
|
|
894
|
+
const time = new Date(envelope.ts).toLocaleTimeString();
|
|
895
|
+
process.stdout.write('\r\x1b[K');
|
|
896
|
+
// Show reputation badge if available
|
|
897
|
+
const repBadge = '';
|
|
898
|
+
console.log(` ${dim(time)} ${bold(cyan(envelope.from.name))}${repBadge}: ${body.text}`);
|
|
899
|
+
rl.prompt(true);
|
|
900
|
+
});
|
|
901
|
+
rooms.on('presence:update', (_roomId, envelope) => {
|
|
902
|
+
const body = envelope.body;
|
|
903
|
+
if (body.status === 'online') {
|
|
904
|
+
process.stdout.write('\r\x1b[K');
|
|
905
|
+
console.log(` ${dim('→')} ${green(envelope.from.name)} is online`);
|
|
906
|
+
rl.prompt(true);
|
|
907
|
+
}
|
|
908
|
+
});
|
|
909
|
+
p2p.on('peer:connected', (peerId) => {
|
|
910
|
+
process.stdout.write('\r\x1b[K');
|
|
911
|
+
console.log(` ${dim('⚡')} Peer connected: ${dim(peerId.slice(0, 20))}...`);
|
|
912
|
+
rl.prompt(true);
|
|
913
|
+
});
|
|
914
|
+
p2p.on('peer:disconnected', (peerId) => {
|
|
915
|
+
process.stdout.write('\r\x1b[K');
|
|
916
|
+
console.log(` ${dim('⚡')} Peer disconnected: ${dim(peerId.slice(0, 20))}...`);
|
|
917
|
+
rl.prompt(true);
|
|
918
|
+
});
|
|
919
|
+
coc.on('chain:opened', (chainId, goal) => {
|
|
920
|
+
process.stdout.write('\r\x1b[K');
|
|
921
|
+
console.log(`\n ${bold(cyan('⛓️ Chain Opened'))}: ${goal.slice(0, 60)}${goal.length > 60 ? '...' : ''}`);
|
|
922
|
+
console.log(` ID: ${dim(chainId)}`);
|
|
923
|
+
rl.prompt(true);
|
|
924
|
+
});
|
|
925
|
+
coc.on('chain:planned', (chainId, dag) => {
|
|
926
|
+
process.stdout.write('\r\x1b[K');
|
|
927
|
+
console.log(` ${bold(cyan('🗺️ Plan Ready'))}: ${dag.length} steps`);
|
|
928
|
+
if (DEBUG) {
|
|
929
|
+
dag.forEach((step, i) => {
|
|
930
|
+
const deps = step.depends_on.length > 0 ? ` ← ${step.depends_on.join(', ')}` : '';
|
|
931
|
+
console.log(` ${i + 1}. [${step.kind}] ${step.title}${deps}`);
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
rl.prompt(true);
|
|
935
|
+
});
|
|
936
|
+
coc.on('step:unlocked', (chainId, stepId, step) => {
|
|
937
|
+
process.stdout.write('\r\x1b[K');
|
|
938
|
+
console.log(` ${bold(yellow('🔓 Step Unlocked'))}: [${stepId}] ${step.title}`);
|
|
939
|
+
rl.prompt(true);
|
|
940
|
+
});
|
|
941
|
+
coc.on('step:assigned', (chainId, stepId, assignee) => {
|
|
942
|
+
process.stdout.write('\r\x1b[K');
|
|
943
|
+
console.log(` ${bold(blue('👤 Assigned'))}: [${stepId}] → ${assignee.slice(0, 20)}...`);
|
|
944
|
+
rl.prompt(true);
|
|
945
|
+
});
|
|
946
|
+
coc.on('step:submitted', (chainId, stepId, status) => {
|
|
947
|
+
process.stdout.write('\r\x1b[K');
|
|
948
|
+
const icon = status === 'completed' ? '✅' : '❌';
|
|
949
|
+
console.log(` ${icon} Step ${stepId}: ${status}`);
|
|
950
|
+
rl.prompt(true);
|
|
951
|
+
});
|
|
952
|
+
coc.on('step:expired', (chainId, stepId, oldAssignee) => {
|
|
953
|
+
process.stdout.write('\r\x1b[K');
|
|
954
|
+
console.log(` ${bold(red('⏰ Lease Expired'))}: [${stepId}] reassigning from ${oldAssignee.slice(0, 16)}...`);
|
|
955
|
+
rl.prompt(true);
|
|
956
|
+
});
|
|
957
|
+
coc.on('chain:completed', (chainId) => {
|
|
958
|
+
process.stdout.write('\r\x1b[K');
|
|
959
|
+
console.log(`\n ${bold(green('✅ Chain Completed'))}: ${chainId}\n`);
|
|
960
|
+
rl.prompt(true);
|
|
961
|
+
});
|
|
962
|
+
// ─── Interactive REPL ───────────────────────────────────────
|
|
963
|
+
const rl = readline.createInterface({
|
|
964
|
+
input: process.stdin,
|
|
965
|
+
output: process.stdout,
|
|
966
|
+
prompt: `${dim(identity.displayName)} ${dim('>')} `,
|
|
967
|
+
});
|
|
968
|
+
rl.prompt();
|
|
969
|
+
let cloudflaredProc = null;
|
|
970
|
+
// Start relay if requested
|
|
971
|
+
if (options.relay) {
|
|
972
|
+
const wsPort = options.port === '0' ? 0 : parseInt(options.port, 10) + 1;
|
|
973
|
+
if (wsPort === 0) {
|
|
974
|
+
console.warn(yellow(' [warn] --relay requires a fixed --port. Ignoring relay.'));
|
|
975
|
+
}
|
|
976
|
+
else {
|
|
977
|
+
// Auto-install cloudflared if missing
|
|
978
|
+
if (!isCommandAvailable('cloudflared')) {
|
|
979
|
+
console.log(` ${yellow('cloudflared not found.')} Installing automatically...`);
|
|
980
|
+
if (!await installCloudflared()) {
|
|
981
|
+
console.warn(yellow(' [warn] Failed to install cloudflared. Skipping relay.'));
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
if (!isCommandAvailable('cloudflared')) {
|
|
985
|
+
console.warn(yellow(' [warn] cloudflared still not available. Skipping relay.'));
|
|
986
|
+
}
|
|
987
|
+
else {
|
|
988
|
+
console.log(` ${dim('🌐')} Spawning cloudflared relay to localhost:${wsPort}...`);
|
|
989
|
+
cloudflaredProc = spawn('cloudflared', ['tunnel', '--url', `http://localhost:${wsPort}`]);
|
|
990
|
+
cloudflaredProc.stderr?.on('data', (data) => {
|
|
991
|
+
const str = data.toString();
|
|
992
|
+
const match = str.match(/(https:\/\/[a-z0-9-]+\.trycloudflare\.com)/);
|
|
993
|
+
if (match) {
|
|
994
|
+
process.stdout.write('\r\x1b[K');
|
|
995
|
+
const host = match[1].replace('https://', '');
|
|
996
|
+
const relayMultiaddr = `/dns4/${host}/tcp/443/wss/p2p/${p2p.getPeerId()}`;
|
|
997
|
+
const code = encodeInvite(relayMultiaddr, options.room);
|
|
998
|
+
console.log(`\n ${bold(green('Relay active!'))} Share this with anyone:`);
|
|
999
|
+
console.log(` ${cyan(`npx society join ${code}`)}`);
|
|
1000
|
+
// Register name in registry
|
|
1001
|
+
registerNode(options.name, {
|
|
1002
|
+
multiaddr: relayMultiaddr,
|
|
1003
|
+
room: options.room,
|
|
1004
|
+
peerId: p2p.getPeerId(),
|
|
1005
|
+
name: options.name,
|
|
1006
|
+
}).then(ok => {
|
|
1007
|
+
if (ok) {
|
|
1008
|
+
console.log(` ${cyan(`npx society join ${options.name}`)}`);
|
|
1009
|
+
}
|
|
1010
|
+
console.log('');
|
|
1011
|
+
rl.prompt(true);
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
} // end cloudflared available check
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
// Command loop
|
|
1019
|
+
rl.on('line', async (line) => {
|
|
1020
|
+
const input = line.trim();
|
|
1021
|
+
if (!input) {
|
|
1022
|
+
rl.prompt();
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
if (input.startsWith('/')) {
|
|
1026
|
+
await handleCommand(input, {
|
|
1027
|
+
identity,
|
|
1028
|
+
p2p,
|
|
1029
|
+
rooms,
|
|
1030
|
+
storage,
|
|
1031
|
+
coc,
|
|
1032
|
+
planner,
|
|
1033
|
+
reputation,
|
|
1034
|
+
exporter,
|
|
1035
|
+
federation,
|
|
1036
|
+
integration,
|
|
1037
|
+
roomId,
|
|
1038
|
+
DEBUG,
|
|
1039
|
+
});
|
|
1040
|
+
rl.prompt();
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
// Send chat message
|
|
1044
|
+
try {
|
|
1045
|
+
await rooms.sendChatMessage(roomId, input, { formatting: 'plain' });
|
|
1046
|
+
const time = new Date().toLocaleTimeString();
|
|
1047
|
+
console.log(` ${dim(time)} ${bold(yellow('you'))}: ${input}`);
|
|
1048
|
+
}
|
|
1049
|
+
catch (err) {
|
|
1050
|
+
console.error(` ${red('Error:')} ${err.message}`);
|
|
1051
|
+
}
|
|
1052
|
+
rl.prompt();
|
|
1053
|
+
});
|
|
1054
|
+
rl.on('close', async () => {
|
|
1055
|
+
console.log('\n[shutdown] Cleaning up...');
|
|
1056
|
+
if (cloudflaredProc) {
|
|
1057
|
+
cloudflaredProc.kill();
|
|
1058
|
+
}
|
|
1059
|
+
stopHeartbeat();
|
|
1060
|
+
proactiveLeader?.destroy();
|
|
1061
|
+
skills.stop();
|
|
1062
|
+
adapterHost.stop();
|
|
1063
|
+
coc.destroy();
|
|
1064
|
+
rooms.destroy();
|
|
1065
|
+
await p2p.stop();
|
|
1066
|
+
storage.close();
|
|
1067
|
+
process.exit(0);
|
|
1068
|
+
});
|
|
1069
|
+
process.on('SIGINT', () => {
|
|
1070
|
+
rl.close();
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
async function handleCommand(input, ctx) {
|
|
1074
|
+
const [cmd, ...args] = input.split(' ');
|
|
1075
|
+
switch (cmd) {
|
|
1076
|
+
case '/peers': {
|
|
1077
|
+
const peers = ctx.p2p.getPeers();
|
|
1078
|
+
const latencies = ctx.p2p.getConnectedPeers().map(p => ({
|
|
1079
|
+
id: p.id.slice(0, 16),
|
|
1080
|
+
latency: p.latency,
|
|
1081
|
+
}));
|
|
1082
|
+
if (peers.length === 0) {
|
|
1083
|
+
console.log(' No connected peers.');
|
|
1084
|
+
}
|
|
1085
|
+
else {
|
|
1086
|
+
console.log(` Connected peers (${peers.length}):`);
|
|
1087
|
+
latencies.forEach(p => {
|
|
1088
|
+
const lat = p.latency ? `${p.latency}ms` : 'N/A';
|
|
1089
|
+
console.log(` • ${p.id}... (${dim(lat)})`);
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
break;
|
|
1093
|
+
}
|
|
1094
|
+
case '/rooms': {
|
|
1095
|
+
const joined = ctx.rooms.getJoinedRooms();
|
|
1096
|
+
console.log(` Joined rooms: ${joined.join(', ')}`);
|
|
1097
|
+
break;
|
|
1098
|
+
}
|
|
1099
|
+
case '/mesh-request': {
|
|
1100
|
+
const sourceFederationId = args[0];
|
|
1101
|
+
const targetFederationDid = args[1];
|
|
1102
|
+
const policyInput = args.slice(2).join(' ').trim();
|
|
1103
|
+
if (!sourceFederationId || !targetFederationDid) {
|
|
1104
|
+
console.log(' Usage: /mesh-request <source_federation_id> <target_federation_did> [policy_json]');
|
|
1105
|
+
break;
|
|
1106
|
+
}
|
|
1107
|
+
try {
|
|
1108
|
+
const policy = policyInput ? JSON.parse(policyInput) : {};
|
|
1109
|
+
const peering = await ctx.federation.requestPeering(sourceFederationId, targetFederationDid, policy);
|
|
1110
|
+
console.log(` ${green('✓')} Peering requested: ${peering.id}`);
|
|
1111
|
+
console.log(` status=${peering.status} target=${peering.targetFederationDid}`);
|
|
1112
|
+
}
|
|
1113
|
+
catch (err) {
|
|
1114
|
+
console.log(` ${red('Error:')} ${err.message}`);
|
|
1115
|
+
}
|
|
1116
|
+
break;
|
|
1117
|
+
}
|
|
1118
|
+
case '/mesh-accept': {
|
|
1119
|
+
const peeringId = args[0];
|
|
1120
|
+
const reason = args.slice(1).join(' ') || undefined;
|
|
1121
|
+
if (!peeringId) {
|
|
1122
|
+
console.log(' Usage: /mesh-accept <peering_id> [reason]');
|
|
1123
|
+
break;
|
|
1124
|
+
}
|
|
1125
|
+
try {
|
|
1126
|
+
const peering = await ctx.federation.respondPeering(peeringId, true, reason);
|
|
1127
|
+
console.log(` ${green('✓')} Peering accepted: ${peering.id}`);
|
|
1128
|
+
}
|
|
1129
|
+
catch (err) {
|
|
1130
|
+
console.log(` ${red('Error:')} ${err.message}`);
|
|
1131
|
+
}
|
|
1132
|
+
break;
|
|
1133
|
+
}
|
|
1134
|
+
case '/mesh-reject': {
|
|
1135
|
+
const peeringId = args[0];
|
|
1136
|
+
const reason = args.slice(1).join(' ') || undefined;
|
|
1137
|
+
if (!peeringId) {
|
|
1138
|
+
console.log(' Usage: /mesh-reject <peering_id> [reason]');
|
|
1139
|
+
break;
|
|
1140
|
+
}
|
|
1141
|
+
try {
|
|
1142
|
+
const peering = await ctx.federation.respondPeering(peeringId, false, reason);
|
|
1143
|
+
console.log(` ${yellow('⚠')} Peering rejected: ${peering.id}`);
|
|
1144
|
+
}
|
|
1145
|
+
catch (err) {
|
|
1146
|
+
console.log(` ${red('Error:')} ${err.message}`);
|
|
1147
|
+
}
|
|
1148
|
+
break;
|
|
1149
|
+
}
|
|
1150
|
+
case '/mesh-revoke': {
|
|
1151
|
+
const peeringId = args[0];
|
|
1152
|
+
const reason = args.slice(1).join(' ') || undefined;
|
|
1153
|
+
if (!peeringId) {
|
|
1154
|
+
console.log(' Usage: /mesh-revoke <peering_id> [reason]');
|
|
1155
|
+
break;
|
|
1156
|
+
}
|
|
1157
|
+
try {
|
|
1158
|
+
const peering = await ctx.federation.revokePeering(peeringId, reason);
|
|
1159
|
+
console.log(` ${yellow('⚠')} Peering revoked: ${peering.id}`);
|
|
1160
|
+
}
|
|
1161
|
+
catch (err) {
|
|
1162
|
+
console.log(` ${red('Error:')} ${err.message}`);
|
|
1163
|
+
}
|
|
1164
|
+
break;
|
|
1165
|
+
}
|
|
1166
|
+
case '/mesh-peerings': {
|
|
1167
|
+
const federationId = args[0];
|
|
1168
|
+
const status = args[1];
|
|
1169
|
+
if (!federationId) {
|
|
1170
|
+
console.log(' Usage: /mesh-peerings <federation_id> [pending|active|rejected|revoked]');
|
|
1171
|
+
break;
|
|
1172
|
+
}
|
|
1173
|
+
const peerings = ctx.federation.listPeerings(federationId, status);
|
|
1174
|
+
if (peerings.length === 0) {
|
|
1175
|
+
console.log(' No peerings found.');
|
|
1176
|
+
break;
|
|
1177
|
+
}
|
|
1178
|
+
console.log(` Peerings (${peerings.length}):`);
|
|
1179
|
+
for (const peering of peerings) {
|
|
1180
|
+
console.log(` • ${peering.id} [${peering.status}] -> ${peering.targetFederationDid}`);
|
|
1181
|
+
}
|
|
1182
|
+
break;
|
|
1183
|
+
}
|
|
1184
|
+
case '/mesh-open': {
|
|
1185
|
+
const peeringId = args[0];
|
|
1186
|
+
const localRoomId = args[1];
|
|
1187
|
+
const remoteRoomId = args[2];
|
|
1188
|
+
const rulesInput = args.slice(3).join(' ').trim();
|
|
1189
|
+
if (!peeringId || !localRoomId || !remoteRoomId) {
|
|
1190
|
+
console.log(' Usage: /mesh-open <peering_id> <local_room_id> <remote_room_id> [rules_json]');
|
|
1191
|
+
break;
|
|
1192
|
+
}
|
|
1193
|
+
try {
|
|
1194
|
+
const rules = rulesInput ? JSON.parse(rulesInput) : undefined;
|
|
1195
|
+
const bridge = await ctx.integration.openMeshBridge(peeringId, localRoomId, remoteRoomId, rules);
|
|
1196
|
+
console.log(` ${green('✓')} Mesh bridge opened: ${bridge.id}`);
|
|
1197
|
+
console.log(` ${bridge.localRoomId} -> ${bridge.remoteRoomId}`);
|
|
1198
|
+
}
|
|
1199
|
+
catch (err) {
|
|
1200
|
+
console.log(` ${red('Error:')} ${err.message}`);
|
|
1201
|
+
}
|
|
1202
|
+
break;
|
|
1203
|
+
}
|
|
1204
|
+
case '/mesh-close': {
|
|
1205
|
+
const bridgeId = args[0];
|
|
1206
|
+
if (!bridgeId) {
|
|
1207
|
+
console.log(' Usage: /mesh-close <bridge_id>');
|
|
1208
|
+
break;
|
|
1209
|
+
}
|
|
1210
|
+
try {
|
|
1211
|
+
await ctx.integration.closeMeshBridge(bridgeId);
|
|
1212
|
+
console.log(` ${green('✓')} Mesh bridge closed: ${bridgeId}`);
|
|
1213
|
+
}
|
|
1214
|
+
catch (err) {
|
|
1215
|
+
console.log(` ${red('Error:')} ${err.message}`);
|
|
1216
|
+
}
|
|
1217
|
+
break;
|
|
1218
|
+
}
|
|
1219
|
+
case '/mesh-bridges': {
|
|
1220
|
+
const federationId = args[0];
|
|
1221
|
+
const bridges = ctx.integration.listMeshBridges(federationId);
|
|
1222
|
+
if (bridges.length === 0) {
|
|
1223
|
+
console.log(' No mesh bridges found.');
|
|
1224
|
+
break;
|
|
1225
|
+
}
|
|
1226
|
+
console.log(` Bridges (${bridges.length}):`);
|
|
1227
|
+
for (const bridge of bridges) {
|
|
1228
|
+
console.log(` • ${bridge.id} [${bridge.status}] ${bridge.localRoomId} -> ${bridge.remoteRoomId} ` +
|
|
1229
|
+
`(in=${bridge.eventsIn}, out=${bridge.eventsOut})`);
|
|
1230
|
+
}
|
|
1231
|
+
break;
|
|
1232
|
+
}
|
|
1233
|
+
case '/mesh-stats': {
|
|
1234
|
+
const federationId = args[0];
|
|
1235
|
+
const stats = ctx.integration.getMeshStats(federationId);
|
|
1236
|
+
console.log(` Mesh stats${federationId ? ` (${federationId})` : ''}:`);
|
|
1237
|
+
console.log(` bridges: ${stats.bridgeCount} (${stats.activeBridges} active)`);
|
|
1238
|
+
console.log(` events: in=${stats.eventsIn} out=${stats.eventsOut}`);
|
|
1239
|
+
if (stats.lastSyncAt) {
|
|
1240
|
+
console.log(` last_sync: ${new Date(stats.lastSyncAt).toISOString()}`);
|
|
1241
|
+
}
|
|
1242
|
+
break;
|
|
1243
|
+
}
|
|
1244
|
+
case '/presence': {
|
|
1245
|
+
const online = ctx.rooms.getOnlinePeers();
|
|
1246
|
+
if (online.length === 0) {
|
|
1247
|
+
console.log(' No online peers detected.');
|
|
1248
|
+
}
|
|
1249
|
+
else {
|
|
1250
|
+
console.log(` Online peers (${online.length}):`);
|
|
1251
|
+
for (const p of online) {
|
|
1252
|
+
const caps = p.capabilities ? JSON.parse(p.capabilities).slice(0, 3).join(', ') : '';
|
|
1253
|
+
console.log(` • ${p.peer_name || 'unknown'} — ${dim(caps)}`);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
break;
|
|
1257
|
+
}
|
|
1258
|
+
case '/reputation': {
|
|
1259
|
+
const targetDid = args[0] || ctx.identity.did;
|
|
1260
|
+
try {
|
|
1261
|
+
const rep = await ctx.reputation.getReputation(targetDid);
|
|
1262
|
+
console.log(` Reputation for ${targetDid.slice(0, 32)}...`);
|
|
1263
|
+
console.log(` Tier: ${formatReputationTier(rep.trust_tier)}`);
|
|
1264
|
+
console.log(` Overall: ${(rep.overall * 100).toFixed(1)}%`);
|
|
1265
|
+
console.log(` Tasks: ${rep.metrics.tasks_completed} completed, ${rep.metrics.tasks_failed} failed`);
|
|
1266
|
+
console.log(` Quality: ${(rep.metrics.avg_quality_score * 100).toFixed(1)}%`);
|
|
1267
|
+
console.log(` On-time: ${(rep.metrics.on_time_delivery * 100).toFixed(1)}%`);
|
|
1268
|
+
if (rep.specialties.length > 0) {
|
|
1269
|
+
console.log(` Top specialties:`);
|
|
1270
|
+
rep.specialties.slice(0, 5).forEach(s => {
|
|
1271
|
+
console.log(` • ${s.specialty}: ${(s.score * 100).toFixed(0)}%`);
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
catch (err) {
|
|
1276
|
+
console.log(` ${red('Error:')} ${err.message}`);
|
|
1277
|
+
}
|
|
1278
|
+
break;
|
|
1279
|
+
}
|
|
1280
|
+
case '/info': {
|
|
1281
|
+
console.log(` Identity: ${ctx.identity.did}`);
|
|
1282
|
+
console.log(` Name: ${ctx.identity.displayName}`);
|
|
1283
|
+
console.log(` PeerId: ${ctx.p2p.getPeerId()}`);
|
|
1284
|
+
console.log(` Addresses: ${ctx.p2p.getMultiaddrs().join(', ')}`);
|
|
1285
|
+
console.log(` Room: ${ctx.roomId}`);
|
|
1286
|
+
console.log(` Peers: ${ctx.p2p.getPeers().length}`);
|
|
1287
|
+
console.log(` Providers: ${ctx.planner.getAvailableProviders().join(', ')}`);
|
|
1288
|
+
break;
|
|
1289
|
+
}
|
|
1290
|
+
case '/history': {
|
|
1291
|
+
const messages = ctx.rooms.getMessages(ctx.roomId, 20);
|
|
1292
|
+
if (messages.length === 0) {
|
|
1293
|
+
console.log(' No messages in history.');
|
|
1294
|
+
}
|
|
1295
|
+
else {
|
|
1296
|
+
messages.reverse().forEach((m) => {
|
|
1297
|
+
const time = new Date(m.ts).toLocaleTimeString();
|
|
1298
|
+
const isMe = m.from_did === ctx.identity.did;
|
|
1299
|
+
const name = isMe ? yellow('you') : cyan(m.from_name ?? 'unknown');
|
|
1300
|
+
console.log(` ${dim(time)} ${bold(name)}: ${m.text}`);
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
break;
|
|
1304
|
+
}
|
|
1305
|
+
case '/summon': {
|
|
1306
|
+
const goal = args.join(' ');
|
|
1307
|
+
if (!goal) {
|
|
1308
|
+
console.log(' Usage: /summon <goal description>');
|
|
1309
|
+
break;
|
|
1310
|
+
}
|
|
1311
|
+
if (!ctx.planner.isReady()) {
|
|
1312
|
+
console.error(` ${red('Error:')} No AI providers available. Set OPENAI_API_KEY or ANTHROPIC_API_KEY.`);
|
|
1313
|
+
break;
|
|
1314
|
+
}
|
|
1315
|
+
console.log(` ${dim('🤖')} Planning collaboration for: "${goal.slice(0, 50)}${goal.length > 50 ? '...' : ''}"`);
|
|
1316
|
+
try {
|
|
1317
|
+
const startTime = Date.now();
|
|
1318
|
+
const planResult = await ctx.planner.generatePlan(goal);
|
|
1319
|
+
const latency = Date.now() - startTime;
|
|
1320
|
+
console.log(` ${green('✓')} Plan generated in ${latency}ms via ${planResult.provider}`);
|
|
1321
|
+
// Open chain
|
|
1322
|
+
const chainId = await ctx.coc.openChain(ctx.roomId, goal, { priority: 'normal' });
|
|
1323
|
+
// Publish plan
|
|
1324
|
+
await ctx.coc.publishPlan(ctx.roomId, chainId, planResult.dag, `${planResult.provider}/${planResult.model}`);
|
|
1325
|
+
}
|
|
1326
|
+
catch (err) {
|
|
1327
|
+
console.error(` ${red('Error:')} ${err.message}`);
|
|
1328
|
+
}
|
|
1329
|
+
break;
|
|
1330
|
+
}
|
|
1331
|
+
case '/template': {
|
|
1332
|
+
const templateId = args[0];
|
|
1333
|
+
const goal = args.slice(1).join(' ');
|
|
1334
|
+
if (!templateId || !goal) {
|
|
1335
|
+
console.log(' Usage: /template <template_name> <goal>');
|
|
1336
|
+
console.log(` Available: ${Object.keys(TEMPLATES).join(', ')}`);
|
|
1337
|
+
break;
|
|
1338
|
+
}
|
|
1339
|
+
try {
|
|
1340
|
+
const template = getTemplate(templateId);
|
|
1341
|
+
console.log(` ${dim('⚡')} Using template "${templateId}"`);
|
|
1342
|
+
const chainId = await ctx.coc.openChain(ctx.roomId, goal, { templateId });
|
|
1343
|
+
const dag = template.generate(goal);
|
|
1344
|
+
await ctx.coc.publishPlan(ctx.roomId, chainId, dag, `template/${templateId}`);
|
|
1345
|
+
}
|
|
1346
|
+
catch (err) {
|
|
1347
|
+
console.error(` ${red('Error:')} ${err.message}`);
|
|
1348
|
+
}
|
|
1349
|
+
break;
|
|
1350
|
+
}
|
|
1351
|
+
case '/chains': {
|
|
1352
|
+
const chains = ctx.coc.getActiveChains();
|
|
1353
|
+
if (chains.length === 0) {
|
|
1354
|
+
console.log(' No active chains.');
|
|
1355
|
+
}
|
|
1356
|
+
else {
|
|
1357
|
+
chains.forEach(c => {
|
|
1358
|
+
const statusColor = c.status === 'completed' ? green : c.status === 'running' ? yellow : red;
|
|
1359
|
+
const progress = c.steps.filter(s => s.status === 'merged' || s.status === 'submitted').length;
|
|
1360
|
+
console.log(` [${statusColor(c.status)}] ${c.chain_id.slice(0, 8)}... — ${progress}/${c.steps.length} steps — ${c.goal.slice(0, 40)}${c.goal.length > 40 ? '...' : ''}`);
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
break;
|
|
1364
|
+
}
|
|
1365
|
+
case '/chain': {
|
|
1366
|
+
const chainId = args[0];
|
|
1367
|
+
if (!chainId) {
|
|
1368
|
+
console.log(' Usage: /chain <chain_id>');
|
|
1369
|
+
break;
|
|
1370
|
+
}
|
|
1371
|
+
const chain = ctx.coc.getChain(chainId);
|
|
1372
|
+
if (!chain) {
|
|
1373
|
+
console.log(' Chain not found.');
|
|
1374
|
+
break;
|
|
1375
|
+
}
|
|
1376
|
+
console.log(` Chain: ${chainId}`);
|
|
1377
|
+
console.log(` Goal: ${chain.goal}`);
|
|
1378
|
+
console.log(` Status: ${chain.status}`);
|
|
1379
|
+
console.log(` Steps:`);
|
|
1380
|
+
chain.steps.forEach(s => {
|
|
1381
|
+
const statusIcon = s.status === 'merged' ? '✅' : s.status === 'assigned' ? '👤' : s.status === 'proposed' ? '○' : '◐';
|
|
1382
|
+
const assignee = s.assignee_did ? ` @${s.assignee_did.slice(0, 8)}...` : '';
|
|
1383
|
+
console.log(` ${statusIcon} [${s.kind}] ${s.title}${assignee}`);
|
|
1384
|
+
});
|
|
1385
|
+
break;
|
|
1386
|
+
}
|
|
1387
|
+
case '/step': {
|
|
1388
|
+
const stepId = args[0];
|
|
1389
|
+
const status = args[1];
|
|
1390
|
+
const memo = args.slice(2).join(' ') || 'Manual submission';
|
|
1391
|
+
if (!stepId || !status) {
|
|
1392
|
+
console.log(' Usage: /step <step_id> <completed|failed> [memo...]');
|
|
1393
|
+
break;
|
|
1394
|
+
}
|
|
1395
|
+
try {
|
|
1396
|
+
// Find chain for this step
|
|
1397
|
+
const chains = ctx.coc.getActiveChains();
|
|
1398
|
+
let chainId = null;
|
|
1399
|
+
for (const chain of chains) {
|
|
1400
|
+
if (chain.steps.find(s => s.step_id === stepId)) {
|
|
1401
|
+
chainId = chain.chain_id;
|
|
1402
|
+
break;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
if (!chainId) {
|
|
1406
|
+
// Try storage
|
|
1407
|
+
const step = ctx.storage.db.prepare('SELECT chain_id FROM coc_steps WHERE step_id = ?').get(stepId);
|
|
1408
|
+
if (step)
|
|
1409
|
+
chainId = step.chain_id;
|
|
1410
|
+
}
|
|
1411
|
+
if (!chainId) {
|
|
1412
|
+
console.log(' Step not found in any chain.');
|
|
1413
|
+
break;
|
|
1414
|
+
}
|
|
1415
|
+
const artifacts = []; // Could parse from input
|
|
1416
|
+
await ctx.coc.submitStep(ctx.roomId, chainId, stepId, status, memo, artifacts);
|
|
1417
|
+
console.log(` ${green('✓')} Step ${stepId} marked as ${status}`);
|
|
1418
|
+
}
|
|
1419
|
+
catch (err) {
|
|
1420
|
+
console.error(` ${red('Error:')} ${err.message}`);
|
|
1421
|
+
}
|
|
1422
|
+
break;
|
|
1423
|
+
}
|
|
1424
|
+
case '/assign': {
|
|
1425
|
+
const [stepId, assignee] = args;
|
|
1426
|
+
if (!stepId || !assignee) {
|
|
1427
|
+
console.log(' Usage: /assign <step_id> <assignee_did>');
|
|
1428
|
+
break;
|
|
1429
|
+
}
|
|
1430
|
+
const chains = ctx.coc.getActiveChains();
|
|
1431
|
+
const chain = chains.find(c => c.steps.find(s => s.step_id === stepId));
|
|
1432
|
+
if (!chain) {
|
|
1433
|
+
console.log(' Step not found.');
|
|
1434
|
+
break;
|
|
1435
|
+
}
|
|
1436
|
+
await ctx.coc.assignStep(ctx.roomId, chain.chain_id, stepId, assignee);
|
|
1437
|
+
console.log(` ${green('✓')} Assigned ${stepId} to ${assignee.slice(0, 20)}...`);
|
|
1438
|
+
break;
|
|
1439
|
+
}
|
|
1440
|
+
case '/review': {
|
|
1441
|
+
const [stepId, decision] = args;
|
|
1442
|
+
const notes = args.slice(2).join(' ') || 'Reviewed';
|
|
1443
|
+
if (!stepId || !['approved', 'rejected', 'needs_revision'].includes(decision)) {
|
|
1444
|
+
console.log(' Usage: /review <step_id> <approved|rejected|needs_revision> [notes...]');
|
|
1445
|
+
break;
|
|
1446
|
+
}
|
|
1447
|
+
const chains = ctx.coc.getActiveChains();
|
|
1448
|
+
const chain = chains.find(c => c.steps.find(s => s.step_id === stepId));
|
|
1449
|
+
if (!chain) {
|
|
1450
|
+
console.log(' Step not found.');
|
|
1451
|
+
break;
|
|
1452
|
+
}
|
|
1453
|
+
await ctx.coc.reviewStep(ctx.roomId, chain.chain_id, stepId, decision, notes);
|
|
1454
|
+
console.log(` ${green('✓')} Reviewed ${stepId} as ${decision}`);
|
|
1455
|
+
break;
|
|
1456
|
+
}
|
|
1457
|
+
case '/cancel': {
|
|
1458
|
+
const chainId = args[0];
|
|
1459
|
+
if (!chainId) {
|
|
1460
|
+
console.log(' Usage: /cancel <chain_id>');
|
|
1461
|
+
break;
|
|
1462
|
+
}
|
|
1463
|
+
await ctx.coc.closeChain(ctx.roomId, chainId, 'cancelled', 'Cancelled by user');
|
|
1464
|
+
console.log(` ${yellow('⚠')} Chain ${chainId.slice(0, 8)}... cancelled`);
|
|
1465
|
+
break;
|
|
1466
|
+
}
|
|
1467
|
+
case '/export': {
|
|
1468
|
+
const chainId = args[0];
|
|
1469
|
+
if (!chainId) {
|
|
1470
|
+
console.log(' Usage: /export <chain_id>');
|
|
1471
|
+
break;
|
|
1472
|
+
}
|
|
1473
|
+
console.log(` ${dim('📦')} Packaging chain ${chainId.slice(0, 8)}... into a .society Capsule...`);
|
|
1474
|
+
try {
|
|
1475
|
+
const outputPath = await ctx.exporter.export(chainId, process.cwd());
|
|
1476
|
+
console.log(` ${bold(green('✅ Export Complete!'))}`);
|
|
1477
|
+
console.log(` ${cyan(outputPath)}`);
|
|
1478
|
+
}
|
|
1479
|
+
catch (err) {
|
|
1480
|
+
console.error(` ${red('Export Error:')} ${err.message}`);
|
|
1481
|
+
}
|
|
1482
|
+
break;
|
|
1483
|
+
}
|
|
1484
|
+
case '/cache': {
|
|
1485
|
+
const stats = ctx.planner.getCacheStats();
|
|
1486
|
+
console.log(` Planner cache: ${stats.size}/${stats.maxSize} entries`);
|
|
1487
|
+
if (ctx.DEBUG && stats.keys.length > 0) {
|
|
1488
|
+
console.log(` Keys: ${stats.keys.slice(0, 10).join(', ')}${stats.keys.length > 10 ? '...' : ''}`);
|
|
1489
|
+
}
|
|
1490
|
+
break;
|
|
1491
|
+
}
|
|
1492
|
+
case '/debug': {
|
|
1493
|
+
console.log(` Debug mode: ${ctx.DEBUG ? 'ON' : 'OFF'}`);
|
|
1494
|
+
break;
|
|
1495
|
+
}
|
|
1496
|
+
case '/quit':
|
|
1497
|
+
process.emit('SIGINT', 'SIGINT');
|
|
1498
|
+
break;
|
|
1499
|
+
default:
|
|
1500
|
+
console.log(` Unknown command: ${cmd}`);
|
|
1501
|
+
console.log(' Available: /peers /rooms /presence /reputation /info /history');
|
|
1502
|
+
console.log(' /summon /template /chains /chain /step /assign /review');
|
|
1503
|
+
console.log(' /cancel /export /cache /debug /quit');
|
|
1504
|
+
console.log(' /mesh-request /mesh-accept /mesh-reject /mesh-revoke /mesh-peerings');
|
|
1505
|
+
console.log(' /mesh-open /mesh-close /mesh-bridges /mesh-stats');
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
// ─── ANSI Color Helpers ─────────────────────────────────────────
|
|
1509
|
+
function bold(s) { return `\x1b[1m${s}\x1b[22m`; }
|
|
1510
|
+
function dim(s) { return `\x1b[2m${s}\x1b[22m`; }
|
|
1511
|
+
function cyan(s) { return `\x1b[36m${s}\x1b[39m`; }
|
|
1512
|
+
function green(s) { return `\x1b[32m${s}\x1b[39m`; }
|
|
1513
|
+
function yellow(s) { return `\x1b[33m${s}\x1b[39m`; }
|
|
1514
|
+
function red(s) { return `\x1b[31m${s}\x1b[39m`; }
|
|
1515
|
+
function blue(s) { return `\x1b[34m${s}\x1b[39m`; }
|
|
1516
|
+
// ─── Cloudflared Auto-Install ────────────────────────────────────
|
|
1517
|
+
function isCommandAvailable(cmd) {
|
|
1518
|
+
try {
|
|
1519
|
+
execSync(`command -v ${cmd}`, { stdio: 'ignore' });
|
|
1520
|
+
return true;
|
|
1521
|
+
}
|
|
1522
|
+
catch {
|
|
1523
|
+
return false;
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
async function installCloudflared() {
|
|
1527
|
+
const os = process.platform;
|
|
1528
|
+
const arch = process.arch;
|
|
1529
|
+
try {
|
|
1530
|
+
if (os === 'darwin') {
|
|
1531
|
+
// macOS — try Homebrew first
|
|
1532
|
+
if (isCommandAvailable('brew')) {
|
|
1533
|
+
console.log(` ${dim(' brew install cloudflare/cloudflare/cloudflared')}`);
|
|
1534
|
+
execSync('brew install cloudflare/cloudflare/cloudflared', { stdio: 'inherit' });
|
|
1535
|
+
return true;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
if (os === 'linux') {
|
|
1539
|
+
// Linux — download binary directly
|
|
1540
|
+
const archMap = {
|
|
1541
|
+
'x64': 'amd64',
|
|
1542
|
+
'arm64': 'arm64',
|
|
1543
|
+
'arm': 'arm',
|
|
1544
|
+
};
|
|
1545
|
+
const cfArch = archMap[arch] || 'amd64';
|
|
1546
|
+
const url = `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${cfArch}`;
|
|
1547
|
+
const dest = '/usr/local/bin/cloudflared';
|
|
1548
|
+
console.log(` ${dim(` Downloading cloudflared for linux-${cfArch}...`)}`);
|
|
1549
|
+
if (isCommandAvailable('curl')) {
|
|
1550
|
+
execSync(`curl -fsSL "${url}" -o /tmp/cloudflared && chmod +x /tmp/cloudflared && sudo mv /tmp/cloudflared ${dest}`, { stdio: 'inherit' });
|
|
1551
|
+
}
|
|
1552
|
+
else if (isCommandAvailable('wget')) {
|
|
1553
|
+
execSync(`wget -q "${url}" -O /tmp/cloudflared && chmod +x /tmp/cloudflared && sudo mv /tmp/cloudflared ${dest}`, { stdio: 'inherit' });
|
|
1554
|
+
}
|
|
1555
|
+
else {
|
|
1556
|
+
console.error(red(' Neither curl nor wget found. Cannot download cloudflared.'));
|
|
1557
|
+
return false;
|
|
1558
|
+
}
|
|
1559
|
+
return true;
|
|
1560
|
+
}
|
|
1561
|
+
// Windows or unsupported
|
|
1562
|
+
console.log(yellow(` Auto-install not supported on ${os}. Install manually:`));
|
|
1563
|
+
console.log(dim(' https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/'));
|
|
1564
|
+
return false;
|
|
1565
|
+
}
|
|
1566
|
+
catch (err) {
|
|
1567
|
+
console.error(red(` Failed to install cloudflared: ${err.message}`));
|
|
1568
|
+
console.log(dim(' Install manually: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/'));
|
|
1569
|
+
return false;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
//# sourceMappingURL=index.js.map
|