@gns-foundation/hive-worker 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/README.md +87 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +355 -0
- package/dist/cli.js.map +1 -0
- package/dist/dashboard.d.ts +25 -0
- package/dist/dashboard.js +97 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/executor.d.ts +16 -0
- package/dist/executor.js +201 -0
- package/dist/executor.js.map +1 -0
- package/dist/hardware.d.ts +19 -0
- package/dist/hardware.js +129 -0
- package/dist/hardware.js.map +1 -0
- package/dist/identity.d.ts +15 -0
- package/dist/identity.js +64 -0
- package/dist/identity.js.map +1 -0
- package/dist/jobs.d.ts +53 -0
- package/dist/jobs.js +133 -0
- package/dist/jobs.js.map +1 -0
- package/dist/llama.d.ts +12 -0
- package/dist/llama.js +65 -0
- package/dist/llama.js.map +1 -0
- package/dist/registry.d.ts +25 -0
- package/dist/registry.js +97 -0
- package/dist/registry.js.map +1 -0
- package/dist/settlement.d.ts +15 -0
- package/dist/settlement.js +112 -0
- package/dist/settlement.js.map +1 -0
- package/package.json +34 -0
- package/src/cli.ts +418 -0
- package/src/dashboard.ts +129 -0
- package/src/executor.ts +270 -0
- package/src/hardware.ts +137 -0
- package/src/identity.ts +82 -0
- package/src/jobs.ts +228 -0
- package/src/llama.ts +79 -0
- package/src/registry.ts +160 -0
- package/src/settlement.ts +145 -0
- package/tsconfig.json +16 -0
package/dist/llama.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ChildProcess } from 'child_process';
|
|
2
|
+
export declare const DEFAULT_RPC_PORT = 50052;
|
|
3
|
+
export interface LlamaRpcHandle {
|
|
4
|
+
process: ChildProcess;
|
|
5
|
+
port: number;
|
|
6
|
+
pid: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function findRpcBinary(): string | null;
|
|
9
|
+
export declare function isRpcServerAvailable(): boolean;
|
|
10
|
+
export declare function startRpcServer(port?: number, onLog?: (line: string) => void): LlamaRpcHandle | null;
|
|
11
|
+
export declare function stopRpcServer(handle: LlamaRpcHandle): void;
|
|
12
|
+
export declare function isPortFree(port: number): Promise<boolean>;
|
package/dist/llama.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// HIVE WORKER — LLAMA.CPP RPC SERVER
|
|
3
|
+
// Start / stop the llama.cpp rpc-server process
|
|
4
|
+
// Detects binary location, manages lifecycle
|
|
5
|
+
// ============================================================
|
|
6
|
+
import { spawn } from 'child_process';
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
import os from 'os';
|
|
9
|
+
export const DEFAULT_RPC_PORT = 50052;
|
|
10
|
+
const RPC_BINARY_CANDIDATES = [
|
|
11
|
+
'rpc-server',
|
|
12
|
+
'llama-rpc-server',
|
|
13
|
+
'/usr/local/bin/rpc-server',
|
|
14
|
+
`${os.homedir()}/llama.cpp/rpc-server`,
|
|
15
|
+
`${os.homedir()}/llama.cpp/build/bin/rpc-server`,
|
|
16
|
+
`${os.homedir()}/llama.cpp/build/rpc-server`,
|
|
17
|
+
];
|
|
18
|
+
export function findRpcBinary() {
|
|
19
|
+
for (const candidate of RPC_BINARY_CANDIDATES) {
|
|
20
|
+
try {
|
|
21
|
+
execSync(`which "${candidate}" 2>/dev/null || test -f "${candidate}"`, { timeout: 2000 });
|
|
22
|
+
return candidate;
|
|
23
|
+
}
|
|
24
|
+
catch { }
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
execSync('rpc-server --help 2>&1 | head -1', { timeout: 2000 });
|
|
28
|
+
return 'rpc-server';
|
|
29
|
+
}
|
|
30
|
+
catch { }
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
export function isRpcServerAvailable() {
|
|
34
|
+
return findRpcBinary() !== null;
|
|
35
|
+
}
|
|
36
|
+
export function startRpcServer(port = DEFAULT_RPC_PORT, onLog) {
|
|
37
|
+
const binary = findRpcBinary();
|
|
38
|
+
if (!binary)
|
|
39
|
+
return null;
|
|
40
|
+
const isAppleSilicon = os.platform() === 'darwin' && os.arch() === 'arm64';
|
|
41
|
+
const args = ['--host', '0.0.0.0', '--port', String(port)];
|
|
42
|
+
if (isAppleSilicon)
|
|
43
|
+
args.push('-d', 'CPU');
|
|
44
|
+
const proc = spawn(binary, args, {
|
|
45
|
+
detached: false,
|
|
46
|
+
stdio: ['ignore', 'ignore', 'ignore'],
|
|
47
|
+
});
|
|
48
|
+
return { process: proc, port, pid: proc.pid ?? 0 };
|
|
49
|
+
}
|
|
50
|
+
export function stopRpcServer(handle) {
|
|
51
|
+
try {
|
|
52
|
+
handle.process.kill('SIGTERM');
|
|
53
|
+
}
|
|
54
|
+
catch { }
|
|
55
|
+
}
|
|
56
|
+
export async function isPortFree(port) {
|
|
57
|
+
const net = await import('net');
|
|
58
|
+
return new Promise((resolve) => {
|
|
59
|
+
const server = net.createServer();
|
|
60
|
+
server.once('error', () => resolve(false));
|
|
61
|
+
server.once('listening', () => { server.close(); resolve(true); });
|
|
62
|
+
server.listen(port, '127.0.0.1');
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=llama.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llama.js","sourceRoot":"","sources":["../src/llama.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { HiveIdentity } from './identity.js';
|
|
2
|
+
import type { HardwareProfile, GeoProfile } from './hardware.js';
|
|
3
|
+
export type WorkerStatus = 'idle' | 'computing' | 'offline';
|
|
4
|
+
export interface SwarmNode {
|
|
5
|
+
pk: string;
|
|
6
|
+
h3_cell: string;
|
|
7
|
+
handle: string | null;
|
|
8
|
+
hardware: HardwareProfile;
|
|
9
|
+
geo: GeoProfile;
|
|
10
|
+
status: WorkerStatus;
|
|
11
|
+
last_heartbeat: string;
|
|
12
|
+
tokens_earned: number;
|
|
13
|
+
rpc_port: number | null;
|
|
14
|
+
worker_version: string;
|
|
15
|
+
}
|
|
16
|
+
export interface RegistryStats {
|
|
17
|
+
totalNodes: number;
|
|
18
|
+
activeNodes: number;
|
|
19
|
+
totalTflops: number;
|
|
20
|
+
}
|
|
21
|
+
export declare function registerNode(identity: HiveIdentity, hw: HardwareProfile, geo: GeoProfile, handle: string | null, rpcPort: number | null): Promise<void>;
|
|
22
|
+
export declare function heartbeat(pk: string, status: WorkerStatus): Promise<void>;
|
|
23
|
+
export declare function deregisterNode(pk: string): Promise<void>;
|
|
24
|
+
export declare function fetchSwarmStats(): Promise<RegistryStats>;
|
|
25
|
+
export declare function fetchTokenBalance(pk: string): Promise<number>;
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// HIVE WORKER — REGISTRY
|
|
3
|
+
// Supabase swarm_nodes: register, heartbeat, deregister
|
|
4
|
+
// Matches the schema built on March 25, 2026
|
|
5
|
+
// ============================================================
|
|
6
|
+
const SUPABASE_URL = 'https://kaqwkxfaclyqjlfhxrmt.supabase.co';
|
|
7
|
+
const SUPABASE_ANON = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImthcXdreGZhY2x5cWpsZmh4cm10Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI4MzU4NTAsImV4cCI6MjA4ODQxMTg1MH0.ClyWNGRxQjpKYzIROPZBqTXDsWvJioGe9pQymDOYBTc';
|
|
8
|
+
const HEADERS = {
|
|
9
|
+
'apikey': SUPABASE_ANON,
|
|
10
|
+
'Authorization': `Bearer ${SUPABASE_ANON}`,
|
|
11
|
+
'Content-Type': 'application/json',
|
|
12
|
+
'Prefer': 'resolution=merge-duplicates,return=minimal',
|
|
13
|
+
};
|
|
14
|
+
async function supabase(method, table, body, query) {
|
|
15
|
+
const url = `${SUPABASE_URL}/rest/v1/${table}${query ? '?' + query : ''}`;
|
|
16
|
+
return fetch(url, {
|
|
17
|
+
method,
|
|
18
|
+
headers: HEADERS,
|
|
19
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
// ─── Registration ─────────────────────────────────────────────
|
|
23
|
+
export async function registerNode(identity, hw, geo, handle, rpcPort) {
|
|
24
|
+
const node = {
|
|
25
|
+
pk: identity.pk,
|
|
26
|
+
h3_cell: geo.h3Cell,
|
|
27
|
+
handle,
|
|
28
|
+
hardware: hw,
|
|
29
|
+
geo,
|
|
30
|
+
status: 'idle',
|
|
31
|
+
last_heartbeat: new Date().toISOString(),
|
|
32
|
+
tokens_earned: await getExistingTokens(identity.pk),
|
|
33
|
+
rpc_port: rpcPort,
|
|
34
|
+
worker_version: '0.1.0',
|
|
35
|
+
};
|
|
36
|
+
// Upsert — on conflict update everything except tokens_earned
|
|
37
|
+
const resp = await supabase('POST', 'swarm_nodes', node, 'on_conflict=pk');
|
|
38
|
+
if (!resp.ok) {
|
|
39
|
+
const err = await resp.text();
|
|
40
|
+
throw new Error(`Registry registration failed: ${resp.status} ${err}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function getExistingTokens(pk) {
|
|
44
|
+
try {
|
|
45
|
+
const resp = await supabase('GET', 'swarm_nodes', undefined, `pk=eq.${pk}&select=tokens_earned`);
|
|
46
|
+
if (!resp.ok)
|
|
47
|
+
return 0;
|
|
48
|
+
const rows = await resp.json();
|
|
49
|
+
return rows[0]?.tokens_earned ?? 0;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// ─── Heartbeat ─────────────────────────────────────────────
|
|
56
|
+
export async function heartbeat(pk, status) {
|
|
57
|
+
const resp = await supabase('PATCH', 'swarm_nodes', { status, last_heartbeat: new Date().toISOString() }, `pk=eq.${pk}`);
|
|
58
|
+
if (!resp.ok && resp.status !== 204) {
|
|
59
|
+
throw new Error(`Heartbeat failed: ${resp.status}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// ─── Deregister (graceful shutdown) ─────────────────────────
|
|
63
|
+
export async function deregisterNode(pk) {
|
|
64
|
+
await supabase('PATCH', 'swarm_nodes', { status: 'offline', last_heartbeat: new Date().toISOString() }, `pk=eq.${pk}`);
|
|
65
|
+
}
|
|
66
|
+
// ─── Stats (for dashboard) ──────────────────────────────────
|
|
67
|
+
export async function fetchSwarmStats() {
|
|
68
|
+
try {
|
|
69
|
+
const resp = await supabase('GET', 'swarm_nodes', undefined, 'select=pk,status,hardware');
|
|
70
|
+
if (!resp.ok)
|
|
71
|
+
return { totalNodes: 0, activeNodes: 0, totalTflops: 0 };
|
|
72
|
+
const rows = await resp.json();
|
|
73
|
+
const totalNodes = rows.length;
|
|
74
|
+
const activeNodes = rows.filter(r => r.status !== 'offline').length;
|
|
75
|
+
const totalTflops = rows
|
|
76
|
+
.filter(r => r.status !== 'offline')
|
|
77
|
+
.reduce((sum, r) => sum + (r.hardware?.estimatedTflops ?? 0), 0);
|
|
78
|
+
return { totalNodes, activeNodes, totalTflops: Math.round(totalTflops * 10) / 10 };
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return { totalNodes: 0, activeNodes: 0, totalTflops: 0 };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// ─── Token balance ──────────────────────────────────────────
|
|
85
|
+
export async function fetchTokenBalance(pk) {
|
|
86
|
+
try {
|
|
87
|
+
const resp = await supabase('GET', 'swarm_nodes', undefined, `pk=eq.${pk}&select=tokens_earned`);
|
|
88
|
+
if (!resp.ok)
|
|
89
|
+
return 0;
|
|
90
|
+
const rows = await resp.json();
|
|
91
|
+
return rows[0]?.tokens_earned ?? 0;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const SPLIT: {
|
|
2
|
+
readonly WORKER: 0.6;
|
|
3
|
+
readonly PLATFORM: 0.25;
|
|
4
|
+
readonly HYDRATION: 0.1;
|
|
5
|
+
readonly SOVEREIGN: 0.05;
|
|
6
|
+
};
|
|
7
|
+
export interface SettlementRecord {
|
|
8
|
+
jobId: string;
|
|
9
|
+
workerPk: string;
|
|
10
|
+
grossReward: number;
|
|
11
|
+
workerSlice: number;
|
|
12
|
+
settledAt: string;
|
|
13
|
+
stellarTxHash: string | null;
|
|
14
|
+
}
|
|
15
|
+
export declare function creditWorker(workerPk: string, jobId: string, grossReward: number): Promise<SettlementRecord>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// HIVE WORKER — SETTLEMENT
|
|
3
|
+
// After a job completes, credit GNS to the worker's
|
|
4
|
+
// tokens_earned balance in Supabase.
|
|
5
|
+
//
|
|
6
|
+
// Real Stellar settlement (the 60/25/10/5 split) runs
|
|
7
|
+
// server-side in the orchestrator. The worker just reads
|
|
8
|
+
// its credited balance here.
|
|
9
|
+
//
|
|
10
|
+
// This module:
|
|
11
|
+
// 1. Increments tokens_earned in swarm_nodes (optimistic)
|
|
12
|
+
// 2. Marks job.settled = true
|
|
13
|
+
// 3. Polls for the actual Stellar TX hash from the orchestrator
|
|
14
|
+
// ============================================================
|
|
15
|
+
const SUPABASE_URL = 'https://kaqwkxfaclyqjlfhxrmt.supabase.co';
|
|
16
|
+
const SUPABASE_ANON = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImthcXdreGZhY2x5cWpsZmh4cm10Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI4MzU4NTAsImV4cCI6MjA4ODQxMTg1MH0.ClyWNGRxQjpKYzIROPZBqTXDsWvJioGe9pQymDOYBTc';
|
|
17
|
+
const HEADERS = {
|
|
18
|
+
'apikey': SUPABASE_ANON,
|
|
19
|
+
'Authorization': `Bearer ${SUPABASE_ANON}`,
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
'Prefer': 'return=minimal',
|
|
22
|
+
};
|
|
23
|
+
// GNS split ratios (matches whitepaper §8.1 and March 25 settlement)
|
|
24
|
+
export const SPLIT = {
|
|
25
|
+
WORKER: 0.60, // community node operators
|
|
26
|
+
PLATFORM: 0.25, // GEIANT / ULISSY orchestration
|
|
27
|
+
HYDRATION: 0.10, // hydration & resilience fund
|
|
28
|
+
SOVEREIGN: 0.05, // BFT quorum coordinators
|
|
29
|
+
};
|
|
30
|
+
// ─── Credit worker (local optimistic update) ──────────────────
|
|
31
|
+
// Increments tokens_earned in swarm_nodes by the 60% worker slice.
|
|
32
|
+
// The orchestrator does the actual Stellar TX — we just reflect it here.
|
|
33
|
+
export async function creditWorker(workerPk, jobId, grossReward) {
|
|
34
|
+
const workerSlice = Math.round(grossReward * SPLIT.WORKER * 1_000_000) / 1_000_000;
|
|
35
|
+
// Step 1: increment tokens_earned via Postgres RPC
|
|
36
|
+
await incrementTokens(workerPk, workerSlice);
|
|
37
|
+
// Step 2: mark job settled (tx hash comes from orchestrator async)
|
|
38
|
+
await markJobSettled(jobId, workerSlice);
|
|
39
|
+
// Step 3: poll for Stellar TX hash (up to 30s, non-blocking)
|
|
40
|
+
const stellarTxHash = await pollForTxHash(jobId);
|
|
41
|
+
return {
|
|
42
|
+
jobId,
|
|
43
|
+
workerPk,
|
|
44
|
+
grossReward,
|
|
45
|
+
workerSlice,
|
|
46
|
+
settledAt: new Date().toISOString(),
|
|
47
|
+
stellarTxHash,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// ─── Supabase RPC: atomic increment ──────────────────────────
|
|
51
|
+
async function incrementTokens(workerPk, amount) {
|
|
52
|
+
// Use a raw SQL RPC to do atomic increment (no race condition)
|
|
53
|
+
const resp = await fetch(`${SUPABASE_URL}/rest/v1/rpc/increment_worker_tokens`, {
|
|
54
|
+
method: 'POST',
|
|
55
|
+
headers: HEADERS,
|
|
56
|
+
body: JSON.stringify({ p_worker_pk: workerPk, p_amount: amount }),
|
|
57
|
+
});
|
|
58
|
+
if (!resp.ok) {
|
|
59
|
+
// Fallback: PATCH with current + amount (non-atomic, acceptable for v0.1)
|
|
60
|
+
await fallbackIncrementTokens(workerPk, amount);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function fallbackIncrementTokens(workerPk, amount) {
|
|
64
|
+
// Read current balance
|
|
65
|
+
const getResp = await fetch(`${SUPABASE_URL}/rest/v1/swarm_nodes?pk=eq.${workerPk}&select=tokens_earned`, { headers: HEADERS });
|
|
66
|
+
if (!getResp.ok)
|
|
67
|
+
return;
|
|
68
|
+
const rows = await getResp.json();
|
|
69
|
+
const current = rows[0]?.tokens_earned ?? 0;
|
|
70
|
+
// Write new total
|
|
71
|
+
await fetch(`${SUPABASE_URL}/rest/v1/swarm_nodes?pk=eq.${workerPk}`, {
|
|
72
|
+
method: 'PATCH',
|
|
73
|
+
headers: HEADERS,
|
|
74
|
+
body: JSON.stringify({ tokens_earned: current + amount }),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
async function markJobSettled(jobId, workerSlice) {
|
|
78
|
+
await fetch(`${SUPABASE_URL}/rest/v1/hive_jobs?id=eq.${jobId}`, {
|
|
79
|
+
method: 'PATCH',
|
|
80
|
+
headers: HEADERS,
|
|
81
|
+
body: JSON.stringify({ settled: true }),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
async function pollForTxHash(jobId, maxWaitMs = 30_000) {
|
|
85
|
+
const deadline = Date.now() + maxWaitMs;
|
|
86
|
+
while (Date.now() < deadline) {
|
|
87
|
+
await sleep(3000);
|
|
88
|
+
const resp = await fetch(`${SUPABASE_URL}/rest/v1/hive_jobs?id=eq.${jobId}&select=stellar_tx_hash`, { headers: HEADERS });
|
|
89
|
+
if (!resp.ok)
|
|
90
|
+
break;
|
|
91
|
+
const rows = await resp.json();
|
|
92
|
+
const hash = rows[0]?.stellar_tx_hash;
|
|
93
|
+
if (hash)
|
|
94
|
+
return hash;
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
function sleep(ms) {
|
|
99
|
+
return new Promise(res => setTimeout(res, ms));
|
|
100
|
+
}
|
|
101
|
+
// ─── SQL to add to supabase_migration.sql ────────────────────
|
|
102
|
+
// (included here as a comment so it's easy to find)
|
|
103
|
+
//
|
|
104
|
+
// CREATE OR REPLACE FUNCTION increment_worker_tokens(
|
|
105
|
+
// p_worker_pk TEXT,
|
|
106
|
+
// p_amount NUMERIC
|
|
107
|
+
// ) RETURNS VOID AS $$
|
|
108
|
+
// UPDATE swarm_nodes
|
|
109
|
+
// SET tokens_earned = tokens_earned + p_amount
|
|
110
|
+
// WHERE pk = p_worker_pk;
|
|
111
|
+
// $$ LANGUAGE SQL;
|
|
112
|
+
//# sourceMappingURL=settlement.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settlement.js","sourceRoot":"","sources":["../src/settlement.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gns-foundation/hive-worker",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Turn your device into a GEIANT Hive compute node. Earn GNS tokens.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"hive-worker": "./dist/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"dev": "ts-node src/cli.ts",
|
|
11
|
+
"prepublishOnly": "npm run build"
|
|
12
|
+
},
|
|
13
|
+
"keywords": ["geiant", "hive", "gns", "ai", "inference", "decentralized"],
|
|
14
|
+
"author": "GNS Foundation <cayerbe@ulissy.app>",
|
|
15
|
+
"license": "BSL-1.1",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"tweetnacl": "^1.0.3",
|
|
18
|
+
"tweetnacl-util": "^0.15.1",
|
|
19
|
+
"node-fetch": "^3.3.2",
|
|
20
|
+
"h3-js": "^4.1.0",
|
|
21
|
+
"ora": "^8.0.1",
|
|
22
|
+
"chalk": "^5.3.0",
|
|
23
|
+
"commander": "^12.1.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"typescript": "^5.4.5",
|
|
27
|
+
"@types/node": "^20.12.0",
|
|
28
|
+
"ts-node": "^10.9.2"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18.0.0"
|
|
32
|
+
},
|
|
33
|
+
"type": "module"
|
|
34
|
+
}
|