idle-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.
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { startNode } from '../src/node.js';
4
+ import chalk from 'chalk';
5
+
6
+ const program = new Command();
7
+
8
+ program
9
+ .name('idle-node')
10
+ .description('IDLE Protocol node — earn from idle compute')
11
+ .version('0.1.0');
12
+
13
+ program
14
+ .command('start')
15
+ .description('Start the IDLE node and begin processing jobs')
16
+ .requiredOption('--wallet <address>', 'Solana wallet address for payouts')
17
+ .option('--types <types>', 'Comma-separated job types to accept (default: fetch,validate)', 'fetch,validate')
18
+ .option('--poll-interval <ms>', 'Job poll interval in milliseconds', '5000')
19
+ .option('--max-concurrent <n>', 'Max concurrent jobs', '3')
20
+ .option('--supabase-url <url>', 'Supabase project URL', process.env.IDLE_SUPABASE_URL || 'https://vnhzyynewdtfpiynaaqd.supabase.co')
21
+ .option('--supabase-key <key>', 'Supabase anon key', process.env.IDLE_SUPABASE_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZuaHp5eW5ld2R0ZnBpeW5hYXFkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzY5NzgyMDksImV4cCI6MjA5MjU1NDIwOX0.6Sw8p9wnfx67NjhzJGmPWialMG4517Eubj9YyfHPaJc')
22
+ .action(async (opts) => {
23
+ console.log(chalk.green('IDLE Node v0.1.0'));
24
+ console.log(chalk.gray(`Wallet: ${opts.wallet}`));
25
+ console.log(chalk.gray(`Types: ${opts.types}`));
26
+ console.log(chalk.gray(`Poll interval: ${opts.pollInterval}ms`));
27
+ console.log('');
28
+
29
+ try {
30
+ await startNode({
31
+ wallet: opts.wallet,
32
+ types: opts.types.split(','),
33
+ pollInterval: parseInt(opts.pollInterval),
34
+ maxConcurrent: parseInt(opts.maxConcurrent),
35
+ supabaseUrl: opts.supabaseUrl,
36
+ supabaseKey: opts.supabaseKey,
37
+ });
38
+ } catch (err) {
39
+ console.error(chalk.red('Fatal:'), err.message);
40
+ process.exit(1);
41
+ }
42
+ });
43
+
44
+ program
45
+ .command('status')
46
+ .description('Check node status and earnings')
47
+ .requiredOption('--wallet <address>', 'Solana wallet address')
48
+ .option('--supabase-url <url>', 'Supabase project URL', process.env.IDLE_SUPABASE_URL || 'https://vnhzyynewdtfpiynaaqd.supabase.co')
49
+ .option('--supabase-key <key>', 'Supabase anon key', process.env.IDLE_SUPABASE_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZuaHp5eW5ld2R0ZnBpeW5hYXFkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzY5NzgyMDksImV4cCI6MjA5MjU1NDIwOX0.6Sw8p9wnfx67NjhzJGmPWialMG4517Eubj9YyfHPaJc')
50
+ .action(async (opts) => {
51
+ const { checkStatus } = await import('../src/status.js');
52
+ await checkStatus(opts);
53
+ });
54
+
55
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "idle-node",
3
+ "version": "0.1.0",
4
+ "description": "IDLE Protocol node — turn idle compute into revenue",
5
+ "main": "./src/node.js",
6
+ "bin": {
7
+ "idle-node": "bin/idle-node.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "src/"
12
+ ],
13
+ "type": "module",
14
+ "engines": {
15
+ "node": ">=18"
16
+ },
17
+ "keywords": [
18
+ "idle",
19
+ "solana",
20
+ "compute",
21
+ "earnings",
22
+ "decentralized",
23
+ "pay.sh"
24
+ ],
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/georgehspirit-ctrl/vera-edge-pro"
29
+ },
30
+ "dependencies": {
31
+ "@supabase/supabase-js": "^2.45.0",
32
+ "commander": "^12.1.0",
33
+ "chalk": "^5.3.0"
34
+ }
35
+ }
package/src/node.js ADDED
@@ -0,0 +1,148 @@
1
+ import { createClient } from '@supabase/supabase-js';
2
+ import chalk from 'chalk';
3
+
4
+ const EXECUTORS = {
5
+ fetch: async (payload) => {
6
+ const { url, method = 'GET' } = payload;
7
+ const start = Date.now();
8
+ const res = await fetch(url, { method });
9
+ const body = await res.text();
10
+ return {
11
+ status: res.status,
12
+ headers: Object.fromEntries(res.headers.entries()),
13
+ body: body.slice(0, 10000),
14
+ latency_ms: Date.now() - start,
15
+ };
16
+ },
17
+
18
+ validate: async (payload) => {
19
+ const { data, schema } = payload;
20
+ // Basic JSON schema validation
21
+ const errors = [];
22
+ if (schema?.required) {
23
+ for (const field of schema.required) {
24
+ if (!(field in (data || {}))) errors.push(`missing required field: ${field}`);
25
+ }
26
+ }
27
+ return { valid: errors.length === 0, errors };
28
+ },
29
+
30
+ query: async (payload) => {
31
+ // Natural language query placeholder — real implementation
32
+ // would connect to user's configured data source
33
+ return { error: 'query execution requires data source configuration' };
34
+ },
35
+ };
36
+
37
+ export async function startNode(config) {
38
+ if (!config.supabaseUrl || !config.supabaseKey) {
39
+ throw new Error('Supabase URL and key required. Set IDLE_SUPABASE_URL and IDLE_SUPABASE_KEY env vars, or pass --supabase-url and --supabase-key.');
40
+ }
41
+
42
+ const supabase = createClient(config.supabaseUrl, config.supabaseKey);
43
+ let running = 0;
44
+ let totalProcessed = 0;
45
+ let totalEarned = 0;
46
+
47
+ console.log(chalk.green('Node started. Polling for jobs...'));
48
+
49
+ const poll = async () => {
50
+ if (running >= config.maxConcurrent) return;
51
+
52
+ try {
53
+ // Claim a pending job atomically
54
+ const { data: jobs, error } = await supabase
55
+ .rpc('idle_claim_node_job', {
56
+ p_node_wallet: config.wallet,
57
+ p_job_types: config.types,
58
+ p_limit: config.maxConcurrent - running,
59
+ });
60
+
61
+ if (error) {
62
+ console.error(chalk.red('Poll error:'), error.message);
63
+ return;
64
+ }
65
+
66
+ if (!jobs || jobs.length === 0) return;
67
+
68
+ for (const job of jobs) {
69
+ running++;
70
+ processJob(supabase, job, config.wallet).then((earned) => {
71
+ running--;
72
+ totalProcessed++;
73
+ totalEarned += earned;
74
+ if (totalProcessed % 10 === 0) {
75
+ console.log(chalk.cyan(`[stats] ${totalProcessed} jobs | $${totalEarned.toFixed(4)} earned`));
76
+ }
77
+ }).catch((err) => {
78
+ running--;
79
+ console.error(chalk.red(`Job ${job.id} failed:`), err.message);
80
+ });
81
+ }
82
+ } catch (err) {
83
+ console.error(chalk.red('Poll error:'), err.message);
84
+ }
85
+ };
86
+
87
+ // Poll loop
88
+ const interval = setInterval(poll, config.pollInterval);
89
+
90
+ // Graceful shutdown
91
+ const shutdown = () => {
92
+ console.log(chalk.yellow('\nShutting down...'));
93
+ clearInterval(interval);
94
+ console.log(chalk.green(`Processed ${totalProcessed} jobs, earned $${totalEarned.toFixed(4)}`));
95
+ process.exit(0);
96
+ };
97
+
98
+ process.on('SIGINT', shutdown);
99
+ process.on('SIGTERM', shutdown);
100
+
101
+ // Initial poll
102
+ await poll();
103
+ }
104
+
105
+ async function processJob(supabase, job, wallet) {
106
+ const executor = EXECUTORS[job.type];
107
+ if (!executor) {
108
+ await supabase
109
+ .from('idle_jobs')
110
+ .update({ status: 'failed', result: { error: `unsupported type: ${job.type}` } })
111
+ .eq('id', job.id);
112
+ return 0;
113
+ }
114
+
115
+ const start = Date.now();
116
+ console.log(chalk.gray(`[${job.type}] Processing ${job.id.slice(0, 8)}...`));
117
+
118
+ try {
119
+ const result = await executor(job.payload);
120
+ const duration = Date.now() - start;
121
+
122
+ await supabase
123
+ .from('idle_jobs')
124
+ .update({
125
+ status: 'completed',
126
+ result,
127
+ completed_at: new Date().toISOString(),
128
+ node_wallet: wallet,
129
+ duration_ms: duration,
130
+ })
131
+ .eq('id', job.id);
132
+
133
+ const earned = job.node_payout_usd || 0;
134
+ console.log(chalk.green(`[${job.type}] ${job.id.slice(0, 8)} done (${duration}ms) +$${earned.toFixed(4)}`));
135
+ return earned;
136
+ } catch (err) {
137
+ await supabase
138
+ .from('idle_jobs')
139
+ .update({
140
+ status: 'failed',
141
+ result: { error: err.message },
142
+ completed_at: new Date().toISOString(),
143
+ node_wallet: wallet,
144
+ })
145
+ .eq('id', job.id);
146
+ throw err;
147
+ }
148
+ }
package/src/status.js ADDED
@@ -0,0 +1,56 @@
1
+ import { createClient } from '@supabase/supabase-js';
2
+ import chalk from 'chalk';
3
+
4
+ export async function checkStatus(opts) {
5
+ if (!opts.supabaseUrl || !opts.supabaseKey) {
6
+ console.error(chalk.red('Supabase URL and key required.'));
7
+ process.exit(1);
8
+ }
9
+
10
+ const supabase = createClient(opts.supabaseUrl, opts.supabaseKey);
11
+
12
+ // Get jobs for this wallet
13
+ const { data: jobs, error } = await supabase
14
+ .from('idle_jobs')
15
+ .select('status, node_payout_usd, type, completed_at')
16
+ .eq('node_wallet', opts.wallet)
17
+ .order('completed_at', { ascending: false })
18
+ .limit(100);
19
+
20
+ if (error) {
21
+ console.error(chalk.red('Error:'), error.message);
22
+ process.exit(1);
23
+ }
24
+
25
+ if (!jobs || jobs.length === 0) {
26
+ console.log(chalk.yellow('No jobs found for this wallet.'));
27
+ return;
28
+ }
29
+
30
+ const completed = jobs.filter(j => j.status === 'completed');
31
+ const paid = jobs.filter(j => j.status === 'paid');
32
+ const failed = jobs.filter(j => j.status === 'failed');
33
+
34
+ const totalEarned = [...completed, ...paid].reduce((sum, j) => sum + (j.node_payout_usd || 0), 0);
35
+ const totalPaid = paid.reduce((sum, j) => sum + (j.node_payout_usd || 0), 0);
36
+ const pending = totalEarned - totalPaid;
37
+
38
+ console.log(chalk.green('IDLE Node Status'));
39
+ console.log(chalk.gray('─'.repeat(40)));
40
+ console.log(`Wallet: ${opts.wallet}`);
41
+ console.log(`Completed: ${completed.length + paid.length} jobs`);
42
+ console.log(`Failed: ${failed.length} jobs`);
43
+ console.log(`Earned: $${totalEarned.toFixed(4)}`);
44
+ console.log(`Paid out: $${totalPaid.toFixed(4)}`);
45
+ console.log(`Pending: $${pending.toFixed(4)}`);
46
+
47
+ // Type breakdown
48
+ const byType = {};
49
+ for (const j of [...completed, ...paid]) {
50
+ byType[j.type] = (byType[j.type] || 0) + 1;
51
+ }
52
+ console.log(chalk.gray('\nBy type:'));
53
+ for (const [type, count] of Object.entries(byType)) {
54
+ console.log(` ${type}: ${count}`);
55
+ }
56
+ }