anticode-node 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +5 -0
- package/index.ts +119 -0
- package/node-daemon.ts +70 -0
- package/package.json +32 -0
package/bin/cli.js
ADDED
package/index.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import * as fs from 'fs'
|
|
2
|
+
import * as path from 'path'
|
|
3
|
+
import { startNodeDaemon, stopNodeDaemon, getDaemonStatus } from './node-daemon'
|
|
4
|
+
|
|
5
|
+
const NODE_ID_FILE = path.join(process.cwd(), 'data', 'node-identity.json')
|
|
6
|
+
const API_BASE = process.env.NODE_REGISTRY_URL || 'https://antigravity-connect-ia.vercel.app/api/nodes'
|
|
7
|
+
|
|
8
|
+
function loadIdentity(): { nodeId: string; secret: string } | null {
|
|
9
|
+
try {
|
|
10
|
+
if (fs.existsSync(NODE_ID_FILE)) return JSON.parse(fs.readFileSync(NODE_ID_FILE, 'utf-8'))
|
|
11
|
+
} catch {}
|
|
12
|
+
return null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function saveIdentity(id: { nodeId: string; secret: string }) {
|
|
16
|
+
const dir = path.dirname(NODE_ID_FILE)
|
|
17
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
|
|
18
|
+
fs.writeFileSync(NODE_ID_FILE, JSON.stringify(id, null, 2))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function generateId(): string {
|
|
22
|
+
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
|
|
23
|
+
let id = ''
|
|
24
|
+
for (let i = 0; i < 16; i++) id += chars[Math.floor(Math.random() * chars.length)]
|
|
25
|
+
return id
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function cmdJoin() {
|
|
29
|
+
const existing = loadIdentity()
|
|
30
|
+
if (existing) {
|
|
31
|
+
console.log(`Already registered as node: ${existing.nodeId}`)
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
const nodeId = generateId()
|
|
35
|
+
const secret = generateId() + generateId()
|
|
36
|
+
try {
|
|
37
|
+
const res = await fetch(`${API_BASE}/register`, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: { 'Content-Type': 'application/json' },
|
|
40
|
+
body: JSON.stringify({
|
|
41
|
+
nodeId,
|
|
42
|
+
secret,
|
|
43
|
+
hostname: require('os').hostname(),
|
|
44
|
+
platform: process.platform,
|
|
45
|
+
version: require('../package.json').version,
|
|
46
|
+
}),
|
|
47
|
+
})
|
|
48
|
+
if (!res.ok) {
|
|
49
|
+
const err = await res.text()
|
|
50
|
+
console.error(`Registration failed: ${err}`)
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
saveIdentity({ nodeId, secret })
|
|
54
|
+
console.log(`Node registered: ${nodeId}`)
|
|
55
|
+
console.log(`Secret saved to: ${NODE_ID_FILE}`)
|
|
56
|
+
} catch (e: any) {
|
|
57
|
+
console.error(`Cannot reach registry (${API_BASE}): ${e.message}`)
|
|
58
|
+
console.log('Creating local-only node identity...')
|
|
59
|
+
saveIdentity({ nodeId, secret })
|
|
60
|
+
console.log(`Local node ID: ${nodeId}`)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function cmdStart() {
|
|
65
|
+
const identity = loadIdentity()
|
|
66
|
+
if (!identity) {
|
|
67
|
+
console.log('Not registered. Run: anticode-node join')
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
const port = parseInt(process.env.NODE_PORT || '0') || 0
|
|
71
|
+
startNodeDaemon(identity, API_BASE, port)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function cmdStatus() {
|
|
75
|
+
const identity = loadIdentity()
|
|
76
|
+
if (!identity) {
|
|
77
|
+
console.log('Not registered')
|
|
78
|
+
console.log('Run: anticode-node join')
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
console.log(`Node ID: ${identity.nodeId}`)
|
|
82
|
+
console.log(`Status: ${getDaemonStatus()}`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function cmdStop() {
|
|
86
|
+
stopNodeDaemon()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function cmdHelp() {
|
|
90
|
+
console.log(`
|
|
91
|
+
anticode-node — AGIC Network Node
|
|
92
|
+
|
|
93
|
+
Commands:
|
|
94
|
+
join Register this machine as an AGIC network node
|
|
95
|
+
start Start the node daemon (heartbeat every 60s)
|
|
96
|
+
status Show node registration and daemon status
|
|
97
|
+
stop Stop the node daemon
|
|
98
|
+
help Show this help
|
|
99
|
+
`)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const cmd = process.argv[2] || 'help'
|
|
103
|
+
switch (cmd) {
|
|
104
|
+
case 'join':
|
|
105
|
+
cmdJoin()
|
|
106
|
+
break
|
|
107
|
+
case 'start':
|
|
108
|
+
cmdStart()
|
|
109
|
+
break
|
|
110
|
+
case 'status':
|
|
111
|
+
cmdStatus()
|
|
112
|
+
break
|
|
113
|
+
case 'stop':
|
|
114
|
+
cmdStop()
|
|
115
|
+
break
|
|
116
|
+
default:
|
|
117
|
+
cmdHelp()
|
|
118
|
+
break
|
|
119
|
+
}
|
package/node-daemon.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const HEARTBEAT_INTERVAL = 60_000
|
|
2
|
+
let intervalHandle: ReturnType<typeof setInterval> | null = null
|
|
3
|
+
let running = false
|
|
4
|
+
|
|
5
|
+
interface NodeIdentity {
|
|
6
|
+
nodeId: string
|
|
7
|
+
secret: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function sendHeartbeat(identity: NodeIdentity, apiBase: string, services: string[]) {
|
|
11
|
+
try {
|
|
12
|
+
const payload = {
|
|
13
|
+
nodeId: identity.nodeId,
|
|
14
|
+
secret: identity.secret,
|
|
15
|
+
uptime: process.uptime(),
|
|
16
|
+
services,
|
|
17
|
+
timestamp: Date.now(),
|
|
18
|
+
}
|
|
19
|
+
const res = await fetch(`${apiBase}/heartbeat`, {
|
|
20
|
+
method: 'POST',
|
|
21
|
+
headers: { 'Content-Type': 'application/json' },
|
|
22
|
+
body: JSON.stringify(payload),
|
|
23
|
+
})
|
|
24
|
+
if (!res.ok) {
|
|
25
|
+
const err = await res.text()
|
|
26
|
+
console.error(`Heartbeat failed: ${err}`)
|
|
27
|
+
}
|
|
28
|
+
} catch (e: any) {
|
|
29
|
+
// Registry unreachable — daemon continues running locally
|
|
30
|
+
console.error(`Registry unreachable: ${e.message}`)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function startNodeDaemon(identity: NodeIdentity, apiBase: string, port: number) {
|
|
35
|
+
if (running) {
|
|
36
|
+
console.log('Daemon already running')
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
running = true
|
|
40
|
+
const services: string[] = []
|
|
41
|
+
if (port > 0) services.push(`mcp:${port}`)
|
|
42
|
+
|
|
43
|
+
console.log(`Node daemon started (interval: ${HEARTBEAT_INTERVAL / 1000}s)`)
|
|
44
|
+
console.log(`Press Ctrl+C to stop`)
|
|
45
|
+
|
|
46
|
+
sendHeartbeat(identity, apiBase, services)
|
|
47
|
+
intervalHandle = setInterval(() => sendHeartbeat(identity, apiBase, services), HEARTBEAT_INTERVAL)
|
|
48
|
+
|
|
49
|
+
process.on('SIGINT', () => {
|
|
50
|
+
stopNodeDaemon()
|
|
51
|
+
process.exit(0)
|
|
52
|
+
})
|
|
53
|
+
process.on('SIGTERM', () => {
|
|
54
|
+
stopNodeDaemon()
|
|
55
|
+
process.exit(0)
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function stopNodeDaemon() {
|
|
60
|
+
if (intervalHandle) {
|
|
61
|
+
clearInterval(intervalHandle)
|
|
62
|
+
intervalHandle = null
|
|
63
|
+
}
|
|
64
|
+
running = false
|
|
65
|
+
console.log('Node daemon stopped')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function getDaemonStatus(): string {
|
|
69
|
+
return running ? 'running' : 'stopped'
|
|
70
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "anticode-node",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AGIC Network Node Client — run a node, earn AGIC rewards for uptime. Join the sovereign agent network.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"anticode-node": "./bin/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/",
|
|
10
|
+
"*.ts",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"agic",
|
|
15
|
+
"node",
|
|
16
|
+
"network",
|
|
17
|
+
"decentralized",
|
|
18
|
+
"blockchain",
|
|
19
|
+
"agent",
|
|
20
|
+
"autonomous",
|
|
21
|
+
"web3"
|
|
22
|
+
],
|
|
23
|
+
"author": "Antigravity Connect IA",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/josemiguel3125-sketch/live-agent-os-infra"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18"
|
|
31
|
+
}
|
|
32
|
+
}
|