@antseed/cli 0.1.0 → 0.1.2
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 +674 -0
- package/README.md +4 -2
- package/dist/cli/commands/dashboard.js +1 -1
- package/dist/cli/commands/dashboard.js.map +1 -1
- package/dist/cli/commands/seed.js +1 -1
- package/dist/cli/commands/seed.js.map +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/plugins/registry.js +4 -4
- package/dist/plugins/registry.js.map +1 -1
- package/dist/proxy/buyer-proxy.d.ts.map +1 -1
- package/dist/proxy/buyer-proxy.js +141 -27
- package/dist/proxy/buyer-proxy.js.map +1 -1
- package/package.json +19 -17
- package/.env.example +0 -15
- package/src/cli/commands/balance.ts +0 -77
- package/src/cli/commands/browse.ts +0 -113
- package/src/cli/commands/config.ts +0 -271
- package/src/cli/commands/connect.test.ts +0 -69
- package/src/cli/commands/connect.ts +0 -342
- package/src/cli/commands/dashboard.ts +0 -59
- package/src/cli/commands/deposit.ts +0 -61
- package/src/cli/commands/dev.ts +0 -107
- package/src/cli/commands/init.ts +0 -99
- package/src/cli/commands/plugin-create.test.ts +0 -60
- package/src/cli/commands/plugin-create.ts +0 -230
- package/src/cli/commands/plugin.test.ts +0 -55
- package/src/cli/commands/plugin.ts +0 -295
- package/src/cli/commands/profile.ts +0 -95
- package/src/cli/commands/seed.test.ts +0 -70
- package/src/cli/commands/seed.ts +0 -447
- package/src/cli/commands/status.ts +0 -73
- package/src/cli/commands/types.ts +0 -56
- package/src/cli/commands/withdraw.ts +0 -61
- package/src/cli/formatters.ts +0 -64
- package/src/cli/index.ts +0 -46
- package/src/cli/shutdown.ts +0 -38
- package/src/config/defaults.ts +0 -49
- package/src/config/effective.test.ts +0 -80
- package/src/config/effective.ts +0 -119
- package/src/config/loader.test.ts +0 -95
- package/src/config/loader.ts +0 -251
- package/src/config/types.ts +0 -139
- package/src/config/validation.ts +0 -78
- package/src/env/load-env.ts +0 -20
- package/src/plugins/loader.ts +0 -96
- package/src/plugins/manager.ts +0 -66
- package/src/plugins/registry.ts +0 -45
- package/src/proxy/buyer-proxy.ts +0 -604
- package/src/status/node-status.ts +0 -105
- package/tsconfig.json +0 -9
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import type { Command } from 'commander';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Register the `antseed profile` command and its subcommands.
|
|
6
|
-
*/
|
|
7
|
-
export function registerProfileCommand(program: Command): void {
|
|
8
|
-
const profile = program
|
|
9
|
-
.command('profile')
|
|
10
|
-
.description('Manage your peer profile');
|
|
11
|
-
|
|
12
|
-
profile
|
|
13
|
-
.command('show')
|
|
14
|
-
.description('Display your current profile')
|
|
15
|
-
.action(async () => {
|
|
16
|
-
const { readFile } = await import('node:fs/promises');
|
|
17
|
-
const { join } = await import('node:path');
|
|
18
|
-
const { homedir } = await import('node:os');
|
|
19
|
-
|
|
20
|
-
const profilePath = join(homedir(), '.antseed', 'profile.json');
|
|
21
|
-
try {
|
|
22
|
-
const data = await readFile(profilePath, 'utf-8');
|
|
23
|
-
const prof = JSON.parse(data);
|
|
24
|
-
console.log(chalk.bold('Peer Profile'));
|
|
25
|
-
console.log(chalk.dim('\u2500'.repeat(40)));
|
|
26
|
-
console.log(` Name: ${chalk.cyan(prof.displayName)}`);
|
|
27
|
-
console.log(` Description: ${prof.description}`);
|
|
28
|
-
console.log(` Tags: ${(prof.tags || []).join(', ')}`);
|
|
29
|
-
console.log(` Capabilities: ${(prof.capabilities || []).join(', ')}`);
|
|
30
|
-
console.log(` Region: ${prof.region}`);
|
|
31
|
-
console.log(` Languages: ${(prof.languages || []).join(', ')}`);
|
|
32
|
-
if (prof.website) console.log(` Website: ${prof.website}`);
|
|
33
|
-
console.log(` Created: ${new Date(prof.createdAt).toISOString()}`);
|
|
34
|
-
console.log(` Updated: ${new Date(prof.updatedAt).toISOString()}`);
|
|
35
|
-
} catch {
|
|
36
|
-
console.log(chalk.yellow('No profile found. Use "antseed profile set" to create one.'));
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
profile
|
|
41
|
-
.command('set')
|
|
42
|
-
.description('Create or update your profile')
|
|
43
|
-
.requiredOption('--name <name>', 'Display name')
|
|
44
|
-
.option('--description <desc>', 'Description of what you offer', '')
|
|
45
|
-
.option('--tags <tags>', 'Comma-separated tags', '')
|
|
46
|
-
.option('--capabilities <caps>', 'Comma-separated capabilities (inference,agent,skill,tool)', 'inference')
|
|
47
|
-
.option('--region <region>', 'Region', 'unknown')
|
|
48
|
-
.option('--languages <langs>', 'Comma-separated languages', 'en')
|
|
49
|
-
.option('--website <url>', 'Website URL')
|
|
50
|
-
.action(async (options) => {
|
|
51
|
-
const { writeFile, mkdir, readFile } = await import('node:fs/promises');
|
|
52
|
-
const { join } = await import('node:path');
|
|
53
|
-
const { homedir } = await import('node:os');
|
|
54
|
-
|
|
55
|
-
const dir = join(homedir(), '.antseed');
|
|
56
|
-
await mkdir(dir, { recursive: true });
|
|
57
|
-
const profilePath = join(dir, 'profile.json');
|
|
58
|
-
|
|
59
|
-
let existing: Record<string, unknown> = {};
|
|
60
|
-
try {
|
|
61
|
-
existing = JSON.parse(await readFile(profilePath, 'utf-8'));
|
|
62
|
-
} catch {}
|
|
63
|
-
|
|
64
|
-
const now = Date.now();
|
|
65
|
-
const prof = {
|
|
66
|
-
...existing,
|
|
67
|
-
displayName: options.name,
|
|
68
|
-
description: options.description || existing.description || '',
|
|
69
|
-
tags: options.tags ? options.tags.split(',').map((t: string) => t.trim()) : existing.tags || [],
|
|
70
|
-
capabilities: options.capabilities.split(',').map((c: string) => c.trim()),
|
|
71
|
-
region: options.region,
|
|
72
|
-
languages: options.languages.split(',').map((l: string) => l.trim()),
|
|
73
|
-
website: options.website || existing.website,
|
|
74
|
-
createdAt: existing.createdAt || now,
|
|
75
|
-
updatedAt: now,
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
await writeFile(profilePath, JSON.stringify(prof, null, 2));
|
|
79
|
-
console.log(chalk.green('Profile saved successfully.'));
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Register the `antseed peer <peerId>` command.
|
|
85
|
-
*/
|
|
86
|
-
export function registerPeerCommand(program: Command): void {
|
|
87
|
-
program
|
|
88
|
-
.command('peer')
|
|
89
|
-
.description('View a peer\'s profile')
|
|
90
|
-
.argument('<peerId>', 'Peer ID (hex)')
|
|
91
|
-
.action(async (peerId: string) => {
|
|
92
|
-
console.log(chalk.bold(`Peer: ${peerId.slice(0, 16)}...`));
|
|
93
|
-
console.log(chalk.yellow('Connect to the network first to view peer details.'));
|
|
94
|
-
});
|
|
95
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict';
|
|
2
|
-
import test from 'node:test';
|
|
3
|
-
import { createDefaultConfig } from '../../config/defaults.js';
|
|
4
|
-
import { resolveEffectiveSellerConfig } from '../../config/effective.js';
|
|
5
|
-
import {
|
|
6
|
-
buildSellerRuntimeOverridesFromFlags,
|
|
7
|
-
buildSellerPluginRuntimeEnv,
|
|
8
|
-
} from './seed.js';
|
|
9
|
-
|
|
10
|
-
test('seed runtime overrides are runtime-only and win over env/config', () => {
|
|
11
|
-
const config = createDefaultConfig();
|
|
12
|
-
config.seller.reserveFloor = 11;
|
|
13
|
-
config.seller.pricing.defaults.inputUsdPerMillion = 12;
|
|
14
|
-
config.seller.pricing.defaults.outputUsdPerMillion = 18;
|
|
15
|
-
const beforeResolution = JSON.parse(JSON.stringify(config));
|
|
16
|
-
|
|
17
|
-
const env = {
|
|
18
|
-
ANTSEED_SELLER_INPUT_USD_PER_MILLION: '20',
|
|
19
|
-
} as NodeJS.ProcessEnv;
|
|
20
|
-
|
|
21
|
-
const overrides = buildSellerRuntimeOverridesFromFlags({
|
|
22
|
-
reserve: 33,
|
|
23
|
-
inputUsdPerMillion: 44,
|
|
24
|
-
outputUsdPerMillion: 55,
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
const effective = resolveEffectiveSellerConfig({
|
|
28
|
-
config,
|
|
29
|
-
env,
|
|
30
|
-
sellerOverrides: overrides,
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
assert.equal(effective.reserveFloor, 33);
|
|
34
|
-
assert.equal(effective.pricing.defaults.inputUsdPerMillion, 44);
|
|
35
|
-
assert.equal(effective.pricing.defaults.outputUsdPerMillion, 55);
|
|
36
|
-
assert.deepEqual(config, beforeResolution);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test('seed maps effective seller pricing into provider runtime keys', () => {
|
|
40
|
-
const config = createDefaultConfig();
|
|
41
|
-
config.seller.maxConcurrentBuyers = 17;
|
|
42
|
-
config.seller.pricing.defaults.inputUsdPerMillion = 10;
|
|
43
|
-
config.seller.pricing.defaults.outputUsdPerMillion = 20;
|
|
44
|
-
config.seller.pricing.providers = {
|
|
45
|
-
anthropic: {
|
|
46
|
-
defaults: {
|
|
47
|
-
inputUsdPerMillion: 15,
|
|
48
|
-
outputUsdPerMillion: 35,
|
|
49
|
-
},
|
|
50
|
-
models: {
|
|
51
|
-
'claude-sonnet-4-5-20250929': {
|
|
52
|
-
inputUsdPerMillion: 18,
|
|
53
|
-
outputUsdPerMillion: 42,
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const runtimeEnv = buildSellerPluginRuntimeEnv(config.seller, 'anthropic');
|
|
60
|
-
assert.equal(runtimeEnv['ANTSEED_INPUT_USD_PER_MILLION'], '15');
|
|
61
|
-
assert.equal(runtimeEnv['ANTSEED_OUTPUT_USD_PER_MILLION'], '35');
|
|
62
|
-
assert.equal(runtimeEnv['ANTSEED_MAX_CONCURRENCY'], '17');
|
|
63
|
-
|
|
64
|
-
const models = JSON.parse(runtimeEnv['ANTSEED_MODEL_PRICING_JSON'] ?? '{}') as Record<string, {
|
|
65
|
-
inputUsdPerMillion: number;
|
|
66
|
-
outputUsdPerMillion: number;
|
|
67
|
-
}>;
|
|
68
|
-
assert.equal(models['claude-sonnet-4-5-20250929']?.inputUsdPerMillion, 18);
|
|
69
|
-
assert.equal(models['claude-sonnet-4-5-20250929']?.outputUsdPerMillion, 42);
|
|
70
|
-
});
|
package/src/cli/commands/seed.ts
DELETED
|
@@ -1,447 +0,0 @@
|
|
|
1
|
-
import type { Command } from 'commander'
|
|
2
|
-
import chalk from 'chalk'
|
|
3
|
-
import ora from 'ora'
|
|
4
|
-
import { writeFile, unlink } from 'node:fs/promises'
|
|
5
|
-
import { join } from 'node:path'
|
|
6
|
-
import { homedir } from 'node:os'
|
|
7
|
-
import { getGlobalOptions } from './types.js'
|
|
8
|
-
import { loadConfig } from '../../config/loader.js'
|
|
9
|
-
import type { CLIProviderConfig } from '../../config/types.js'
|
|
10
|
-
import { AntseedNode, type Provider, getInstance } from '@antseed/node'
|
|
11
|
-
import type { PaymentConfig } from '@antseed/node/payments'
|
|
12
|
-
import { parseBootstrapList, toBootstrapConfig } from '@antseed/node/discovery'
|
|
13
|
-
import { setupShutdownHandler } from '../shutdown.js'
|
|
14
|
-
import { loadProviderPlugin, buildPluginConfig } from '../../plugins/loader.js'
|
|
15
|
-
import { resolveEffectiveSellerConfig, type SellerRuntimeOverrides } from '../../config/effective.js'
|
|
16
|
-
import type { SellerCLIConfig } from '../../config/types.js'
|
|
17
|
-
|
|
18
|
-
const STATE_FILE = join(homedir(), '.antseed', 'daemon.state.json')
|
|
19
|
-
|
|
20
|
-
/** Map config file provider entry to env-style key/value pairs for the plugin. */
|
|
21
|
-
function providerConfigToEnv(p: CLIProviderConfig): Record<string, string> {
|
|
22
|
-
const env: Record<string, string> = {}
|
|
23
|
-
if (p.authValue) env['ANTHROPIC_API_KEY'] = p.authValue
|
|
24
|
-
if (p.authType) env['ANTSEED_AUTH_TYPE'] = p.authType
|
|
25
|
-
return env
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function parseOptionalBoolEnv(value: string | undefined): boolean | null {
|
|
29
|
-
if (value === undefined) return null
|
|
30
|
-
const normalized = value.trim().toLowerCase()
|
|
31
|
-
if (['1', 'true', 'yes', 'on'].includes(normalized)) return true
|
|
32
|
-
if (['0', 'false', 'no', 'off'].includes(normalized)) return false
|
|
33
|
-
return null
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function isRpcReachable(rpcUrl: string, timeoutMs = 1500): Promise<boolean> {
|
|
37
|
-
const controller = new AbortController()
|
|
38
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs)
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
const response = await fetch(rpcUrl, {
|
|
42
|
-
method: 'POST',
|
|
43
|
-
headers: { 'content-type': 'application/json' },
|
|
44
|
-
body: JSON.stringify({
|
|
45
|
-
jsonrpc: '2.0',
|
|
46
|
-
id: 1,
|
|
47
|
-
method: 'eth_chainId',
|
|
48
|
-
params: [],
|
|
49
|
-
}),
|
|
50
|
-
signal: controller.signal,
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
if (!response.ok) return false
|
|
54
|
-
|
|
55
|
-
const payload = await response.json() as { result?: unknown }
|
|
56
|
-
return typeof payload.result === 'string' && payload.result.startsWith('0x')
|
|
57
|
-
} catch {
|
|
58
|
-
return false
|
|
59
|
-
} finally {
|
|
60
|
-
clearTimeout(timeout)
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function toUSDCBaseUnits(value: string | undefined, fallbackBaseUnits: string): string {
|
|
65
|
-
if (value === undefined) return fallbackBaseUnits
|
|
66
|
-
const parsed = Number.parseFloat(value.trim())
|
|
67
|
-
if (!Number.isFinite(parsed) || parsed <= 0) return fallbackBaseUnits
|
|
68
|
-
return String(Math.round(parsed * 1_000_000))
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function buildSellerRuntimeOverridesFromFlags(options: {
|
|
72
|
-
reserve?: number
|
|
73
|
-
inputUsdPerMillion?: number
|
|
74
|
-
outputUsdPerMillion?: number
|
|
75
|
-
}): SellerRuntimeOverrides {
|
|
76
|
-
const overrides: SellerRuntimeOverrides = {}
|
|
77
|
-
if (options.reserve !== undefined) {
|
|
78
|
-
overrides.reserveFloor = options.reserve
|
|
79
|
-
}
|
|
80
|
-
if (options.inputUsdPerMillion !== undefined) {
|
|
81
|
-
overrides.inputUsdPerMillion = options.inputUsdPerMillion
|
|
82
|
-
}
|
|
83
|
-
if (options.outputUsdPerMillion !== undefined) {
|
|
84
|
-
overrides.outputUsdPerMillion = options.outputUsdPerMillion
|
|
85
|
-
}
|
|
86
|
-
return overrides
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function buildSellerPluginRuntimeEnv(
|
|
90
|
-
sellerConfig: SellerCLIConfig,
|
|
91
|
-
providerName: string,
|
|
92
|
-
): Record<string, string> {
|
|
93
|
-
const providerPricing = sellerConfig.pricing.providers?.[providerName]
|
|
94
|
-
const effectiveDefaults = providerPricing?.defaults ?? sellerConfig.pricing.defaults
|
|
95
|
-
const modelPricing = providerPricing?.models
|
|
96
|
-
const runtimeEnv: Record<string, string> = {
|
|
97
|
-
ANTSEED_INPUT_USD_PER_MILLION: String(effectiveDefaults.inputUsdPerMillion),
|
|
98
|
-
ANTSEED_OUTPUT_USD_PER_MILLION: String(effectiveDefaults.outputUsdPerMillion),
|
|
99
|
-
ANTSEED_MAX_CONCURRENCY: String(sellerConfig.maxConcurrentBuyers),
|
|
100
|
-
}
|
|
101
|
-
if (modelPricing && Object.keys(modelPricing).length > 0) {
|
|
102
|
-
runtimeEnv['ANTSEED_MODEL_PRICING_JSON'] = JSON.stringify(modelPricing)
|
|
103
|
-
}
|
|
104
|
-
return runtimeEnv
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function registerSeedCommand(program: Command): void {
|
|
108
|
-
program
|
|
109
|
-
.command('seed')
|
|
110
|
-
.description('Start seeding your idle LLM capacity to the P2P network')
|
|
111
|
-
.option('--provider <name>', 'provider plugin name (e.g., anthropic)')
|
|
112
|
-
.option('--instance <id>', 'use a configured plugin instance by ID')
|
|
113
|
-
.option('-r, --reserve <number>', 'runtime-only reserve floor override (does not write config file)', parseFloat)
|
|
114
|
-
.option('--input-usd-per-million <number>', 'runtime-only input pricing override in USD per 1M tokens', parseFloat)
|
|
115
|
-
.option('--output-usd-per-million <number>', 'runtime-only output pricing override in USD per 1M tokens', parseFloat)
|
|
116
|
-
.action(async (options) => {
|
|
117
|
-
const globalOpts = getGlobalOptions(program)
|
|
118
|
-
const config = await loadConfig(globalOpts.config)
|
|
119
|
-
const runtimeOverrides = buildSellerRuntimeOverridesFromFlags({
|
|
120
|
-
reserve: options.reserve as number | undefined,
|
|
121
|
-
inputUsdPerMillion: options.inputUsdPerMillion as number | undefined,
|
|
122
|
-
outputUsdPerMillion: options.outputUsdPerMillion as number | undefined,
|
|
123
|
-
})
|
|
124
|
-
const effectiveSellerConfig = resolveEffectiveSellerConfig({
|
|
125
|
-
config,
|
|
126
|
-
sellerOverrides: runtimeOverrides,
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
let provider: Provider
|
|
130
|
-
|
|
131
|
-
if (options.instance) {
|
|
132
|
-
const configPath = join(homedir(), '.antseed', 'config.json')
|
|
133
|
-
const instance = await getInstance(configPath, options.instance)
|
|
134
|
-
if (!instance) {
|
|
135
|
-
console.error(chalk.red(`Instance "${options.instance}" not found.`))
|
|
136
|
-
process.exit(1)
|
|
137
|
-
}
|
|
138
|
-
if (instance.type !== 'provider') {
|
|
139
|
-
console.error(chalk.red(`Instance "${options.instance}" is a ${instance.type}, not a provider.`))
|
|
140
|
-
process.exit(1)
|
|
141
|
-
}
|
|
142
|
-
const spinner = ora(`Loading provider plugin "${instance.package}"...`).start()
|
|
143
|
-
try {
|
|
144
|
-
const plugin = await loadProviderPlugin(instance.package)
|
|
145
|
-
const runtimeEnv = buildSellerPluginRuntimeEnv(effectiveSellerConfig, plugin.name)
|
|
146
|
-
const pluginConfig = buildPluginConfig(plugin.configSchema ?? plugin.configKeys ?? [], runtimeEnv, instance.config as Record<string, string>)
|
|
147
|
-
provider = await plugin.createProvider(pluginConfig)
|
|
148
|
-
if (provider.init) {
|
|
149
|
-
spinner.text = 'Validating credentials...'
|
|
150
|
-
await provider.init()
|
|
151
|
-
}
|
|
152
|
-
spinner.succeed(chalk.green(`Provider "${plugin.displayName}" loaded`))
|
|
153
|
-
} catch (err) {
|
|
154
|
-
spinner.fail(chalk.red(`Failed to load provider: ${(err as Error).message}`))
|
|
155
|
-
process.exit(1)
|
|
156
|
-
}
|
|
157
|
-
} else if (options.provider) {
|
|
158
|
-
const spinner = ora(`Loading provider plugin "${options.provider}"...`).start()
|
|
159
|
-
try {
|
|
160
|
-
const plugin = await loadProviderPlugin(options.provider)
|
|
161
|
-
const configProvider = config.providers.find(p => p.type === options.provider)
|
|
162
|
-
const fileConfig = configProvider ? providerConfigToEnv(configProvider) : {}
|
|
163
|
-
const runtimeEnv = buildSellerPluginRuntimeEnv(effectiveSellerConfig, options.provider as string)
|
|
164
|
-
const envAndRuntimeConfig = buildPluginConfig(plugin.configSchema ?? plugin.configKeys ?? [], runtimeEnv)
|
|
165
|
-
const pluginConfig = { ...fileConfig, ...envAndRuntimeConfig }
|
|
166
|
-
provider = await plugin.createProvider(pluginConfig)
|
|
167
|
-
if (provider.init) {
|
|
168
|
-
spinner.text = 'Validating credentials...'
|
|
169
|
-
await provider.init()
|
|
170
|
-
}
|
|
171
|
-
spinner.succeed(chalk.green(`Provider "${plugin.displayName}" loaded`))
|
|
172
|
-
} catch (err) {
|
|
173
|
-
spinner.fail(chalk.red(`Failed to load provider: ${(err as Error).message}`))
|
|
174
|
-
process.exit(1)
|
|
175
|
-
}
|
|
176
|
-
} else {
|
|
177
|
-
console.error(chalk.red('Error: No provider specified.'))
|
|
178
|
-
console.error(chalk.dim('Run: antseed seed --provider <name> or antseed seed --instance <id>'))
|
|
179
|
-
process.exit(1)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const bootstrapNodes = config.network.bootstrapNodes.length > 0
|
|
183
|
-
? toBootstrapConfig(parseBootstrapList(config.network.bootstrapNodes))
|
|
184
|
-
: undefined
|
|
185
|
-
|
|
186
|
-
const preferredMethod = config.payments.preferredMethod
|
|
187
|
-
const defaultEscrowAmountUSDC = process.env['ANTSEED_DEFAULT_ESCROW_USDC'] ?? config.payments.crypto?.defaultLockAmountUSDC ?? '1'
|
|
188
|
-
const defaultEscrowAmountUSDCBaseUnits = toUSDCBaseUnits(defaultEscrowAmountUSDC, '1000000')
|
|
189
|
-
const settlementIdleMsRaw = process.env['ANTSEED_SETTLEMENT_IDLE_MS']
|
|
190
|
-
const settlementIdleMs = settlementIdleMsRaw ? parseInt(settlementIdleMsRaw, 10) : 30_000
|
|
191
|
-
const sellerWalletAddress = process.env['ANTSEED_SELLER_WALLET_ADDRESS']
|
|
192
|
-
|
|
193
|
-
let paymentConfig: PaymentConfig | null = null
|
|
194
|
-
if (preferredMethod === 'crypto' && config.payments.crypto) {
|
|
195
|
-
const defaultLockAmountUSDCBaseUnits = toUSDCBaseUnits(
|
|
196
|
-
config.payments.crypto.defaultLockAmountUSDC ?? defaultEscrowAmountUSDC,
|
|
197
|
-
defaultEscrowAmountUSDCBaseUnits,
|
|
198
|
-
)
|
|
199
|
-
const cryptoConfig: NonNullable<PaymentConfig['crypto']> = {
|
|
200
|
-
chainId: config.payments.crypto.chainId,
|
|
201
|
-
rpcUrl: config.payments.crypto.rpcUrl,
|
|
202
|
-
contractAddress: config.payments.crypto.escrowContractAddress,
|
|
203
|
-
usdcAddress: config.payments.crypto.usdcContractAddress,
|
|
204
|
-
defaultLockAmountUSDC: defaultLockAmountUSDCBaseUnits,
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
paymentConfig = {
|
|
208
|
-
crypto: cryptoConfig,
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const settlementEnv = parseOptionalBoolEnv(process.env['ANTSEED_ENABLE_SETTLEMENT'])
|
|
213
|
-
let paymentsEnabled = settlementEnv ?? paymentConfig !== null
|
|
214
|
-
const cryptoRpcUrl = paymentConfig?.crypto?.rpcUrl
|
|
215
|
-
|
|
216
|
-
if (paymentsEnabled && cryptoRpcUrl && settlementEnv !== true) {
|
|
217
|
-
const rpcUp = await isRpcReachable(cryptoRpcUrl)
|
|
218
|
-
if (!rpcUp) {
|
|
219
|
-
paymentsEnabled = false
|
|
220
|
-
console.log(chalk.yellow(`Payments disabled: RPC node unreachable at ${cryptoRpcUrl}`))
|
|
221
|
-
console.log(chalk.dim('Start your chain node or set ANTSEED_ENABLE_SETTLEMENT=true to force-enable payments.'))
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const providerName = options.provider as string | undefined ?? provider.name ?? 'unknown'
|
|
226
|
-
const runtimeProviderPricing = buildSellerPluginRuntimeEnv(effectiveSellerConfig, providerName)
|
|
227
|
-
const runtimeInputUsdPerMillion = Number.parseFloat(runtimeProviderPricing['ANTSEED_INPUT_USD_PER_MILLION'] ?? '')
|
|
228
|
-
const runtimeOutputUsdPerMillion = Number.parseFloat(runtimeProviderPricing['ANTSEED_OUTPUT_USD_PER_MILLION'] ?? '')
|
|
229
|
-
let runtimeModelPricing: Record<string, { inputUsdPerMillion: number; outputUsdPerMillion: number }> | undefined
|
|
230
|
-
if (runtimeProviderPricing['ANTSEED_MODEL_PRICING_JSON']) {
|
|
231
|
-
try {
|
|
232
|
-
const parsed = JSON.parse(runtimeProviderPricing['ANTSEED_MODEL_PRICING_JSON']) as unknown
|
|
233
|
-
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
234
|
-
const out: Record<string, { inputUsdPerMillion: number; outputUsdPerMillion: number }> = {}
|
|
235
|
-
for (const [model, pricing] of Object.entries(parsed as Record<string, unknown>)) {
|
|
236
|
-
if (!pricing || typeof pricing !== 'object' || Array.isArray(pricing)) continue
|
|
237
|
-
const input = Number((pricing as Record<string, unknown>)['inputUsdPerMillion'])
|
|
238
|
-
const output = Number((pricing as Record<string, unknown>)['outputUsdPerMillion'])
|
|
239
|
-
if (Number.isFinite(input) && Number.isFinite(output)) {
|
|
240
|
-
out[model] = { inputUsdPerMillion: input, outputUsdPerMillion: output }
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
if (Object.keys(out).length > 0) {
|
|
244
|
-
runtimeModelPricing = out
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
} catch {
|
|
248
|
-
// Ignore malformed model pricing; fallback defaults are still emitted.
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
console.log(chalk.bold('Effective seller settings:'))
|
|
252
|
-
console.log(chalk.dim(` provider: ${providerName}`))
|
|
253
|
-
console.log(
|
|
254
|
-
chalk.dim(
|
|
255
|
-
` pricing defaults (USD/1M): input=${runtimeProviderPricing['ANTSEED_INPUT_USD_PER_MILLION']}, output=${runtimeProviderPricing['ANTSEED_OUTPUT_USD_PER_MILLION']}`
|
|
256
|
-
)
|
|
257
|
-
)
|
|
258
|
-
console.log(
|
|
259
|
-
chalk.dim(
|
|
260
|
-
` enabled providers: ${effectiveSellerConfig.enabledProviders.length > 0 ? effectiveSellerConfig.enabledProviders.join(', ') : '(none)'}`
|
|
261
|
-
)
|
|
262
|
-
)
|
|
263
|
-
console.log(chalk.dim(` reserve floor: ${effectiveSellerConfig.reserveFloor}`))
|
|
264
|
-
console.log(chalk.dim(` max concurrent buyers: ${effectiveSellerConfig.maxConcurrentBuyers}`))
|
|
265
|
-
console.log('')
|
|
266
|
-
|
|
267
|
-
const nodeSpinner = ora('Starting seeding daemon...').start()
|
|
268
|
-
|
|
269
|
-
const node = new AntseedNode({
|
|
270
|
-
role: 'seller',
|
|
271
|
-
bootstrapNodes,
|
|
272
|
-
dataDir: globalOpts.dataDir,
|
|
273
|
-
payments: {
|
|
274
|
-
enabled: paymentsEnabled,
|
|
275
|
-
paymentMethod: preferredMethod,
|
|
276
|
-
platformFeeRate: config.payments.platformFeeRate,
|
|
277
|
-
settlementIdleMs: Number.isFinite(settlementIdleMs) ? settlementIdleMs : 30_000,
|
|
278
|
-
defaultEscrowAmountUSDC: defaultEscrowAmountUSDCBaseUnits,
|
|
279
|
-
sellerWalletAddress,
|
|
280
|
-
paymentConfig,
|
|
281
|
-
},
|
|
282
|
-
})
|
|
283
|
-
|
|
284
|
-
node.registerProvider(provider)
|
|
285
|
-
|
|
286
|
-
try {
|
|
287
|
-
await node.start()
|
|
288
|
-
nodeSpinner.succeed(chalk.green('Seeding active'))
|
|
289
|
-
console.log(chalk.dim(` Peer ID: ${node.peerId ?? 'unknown'}`))
|
|
290
|
-
console.log(chalk.dim(` DHT port: ${node.dhtPort}`))
|
|
291
|
-
console.log(chalk.dim(` Signaling port: ${node.signalingPort}`))
|
|
292
|
-
} catch (err) {
|
|
293
|
-
nodeSpinner.fail(chalk.red(`Failed to start seeding: ${(err as Error).message}`))
|
|
294
|
-
process.exit(1)
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Write daemon state so dashboard and connect can discover this seeder
|
|
298
|
-
const startedAt = Date.now()
|
|
299
|
-
const syntheticSessionStarts = new Map<string, number>()
|
|
300
|
-
let stateWriteInFlight = false
|
|
301
|
-
let stateWritePending = false
|
|
302
|
-
|
|
303
|
-
function formatUptime(): string {
|
|
304
|
-
const ms = Date.now() - startedAt
|
|
305
|
-
const s = Math.floor(ms / 1000)
|
|
306
|
-
if (s < 60) return `${s}s`
|
|
307
|
-
const m = Math.floor(s / 60)
|
|
308
|
-
if (m < 60) return `${m}m ${s % 60}s`
|
|
309
|
-
const h = Math.floor(m / 60)
|
|
310
|
-
return `${h}h ${m % 60}m`
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
function buildDaemonState() {
|
|
314
|
-
const now = Date.now()
|
|
315
|
-
const cap = provider.getCapacity()
|
|
316
|
-
const trackedSessions = node
|
|
317
|
-
.getActiveSellerSessions()
|
|
318
|
-
.filter((session) => !session.settling)
|
|
319
|
-
.map((session) => ({
|
|
320
|
-
sessionId: session.sessionId,
|
|
321
|
-
buyerPeerId: session.buyerPeerId,
|
|
322
|
-
provider: session.provider,
|
|
323
|
-
startedAt: session.startedAt,
|
|
324
|
-
lastActivityAt: session.lastActivityAt,
|
|
325
|
-
totalRequests: session.totalRequests,
|
|
326
|
-
totalTokens: session.totalTokens,
|
|
327
|
-
avgLatencyMs: session.avgLatencyMs,
|
|
328
|
-
}))
|
|
329
|
-
|
|
330
|
-
const syntheticCount = Math.max(0, cap.current - trackedSessions.length)
|
|
331
|
-
const syntheticIds = new Set<string>()
|
|
332
|
-
const syntheticDetails: Array<{
|
|
333
|
-
sessionId: string
|
|
334
|
-
buyerPeerId: string
|
|
335
|
-
provider: string
|
|
336
|
-
startedAt: number
|
|
337
|
-
lastActivityAt: number
|
|
338
|
-
totalRequests: number
|
|
339
|
-
totalTokens: number
|
|
340
|
-
avgLatencyMs: number
|
|
341
|
-
}> = []
|
|
342
|
-
|
|
343
|
-
for (let i = 0; i < syntheticCount; i += 1) {
|
|
344
|
-
const sessionId = `provider-slot-${i + 1}`
|
|
345
|
-
syntheticIds.add(sessionId)
|
|
346
|
-
|
|
347
|
-
const existingStart = syntheticSessionStarts.get(sessionId)
|
|
348
|
-
const startedAtTs = typeof existingStart === 'number' ? existingStart : now
|
|
349
|
-
syntheticSessionStarts.set(sessionId, startedAtTs)
|
|
350
|
-
|
|
351
|
-
syntheticDetails.push({
|
|
352
|
-
sessionId,
|
|
353
|
-
buyerPeerId: 'unknown',
|
|
354
|
-
provider: providerName,
|
|
355
|
-
startedAt: startedAtTs,
|
|
356
|
-
lastActivityAt: now,
|
|
357
|
-
totalRequests: 0,
|
|
358
|
-
totalTokens: 0,
|
|
359
|
-
avgLatencyMs: 0,
|
|
360
|
-
})
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
for (const existingId of syntheticSessionStarts.keys()) {
|
|
364
|
-
if (!syntheticIds.has(existingId)) {
|
|
365
|
-
syntheticSessionStarts.delete(existingId)
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const activeSessionDetails = [...trackedSessions, ...syntheticDetails]
|
|
370
|
-
const activeSessionsCount = Math.max(node.getActiveSellerSessionCount(), cap.current)
|
|
371
|
-
|
|
372
|
-
return {
|
|
373
|
-
state: 'seeding',
|
|
374
|
-
pid: process.pid,
|
|
375
|
-
peerId: node.peerId,
|
|
376
|
-
dhtPort: node.dhtPort,
|
|
377
|
-
signalingPort: node.signalingPort,
|
|
378
|
-
provider: providerName,
|
|
379
|
-
defaultInputUsdPerMillion: Number.isFinite(runtimeInputUsdPerMillion) ? runtimeInputUsdPerMillion : undefined,
|
|
380
|
-
defaultOutputUsdPerMillion: Number.isFinite(runtimeOutputUsdPerMillion) ? runtimeOutputUsdPerMillion : undefined,
|
|
381
|
-
providerPricing: {
|
|
382
|
-
[providerName]: {
|
|
383
|
-
defaults: {
|
|
384
|
-
inputUsdPerMillion: Number.isFinite(runtimeInputUsdPerMillion) ? runtimeInputUsdPerMillion : 0,
|
|
385
|
-
outputUsdPerMillion: Number.isFinite(runtimeOutputUsdPerMillion) ? runtimeOutputUsdPerMillion : 0,
|
|
386
|
-
},
|
|
387
|
-
...(runtimeModelPricing ? { models: runtimeModelPricing } : {}),
|
|
388
|
-
},
|
|
389
|
-
},
|
|
390
|
-
startedAt,
|
|
391
|
-
// Fields the dashboard reads
|
|
392
|
-
peerCount: 0,
|
|
393
|
-
activeSessions: activeSessionsCount,
|
|
394
|
-
activeSessionDetails,
|
|
395
|
-
capacityUsedPercent: cap.max > 0 ? Math.round((cap.current / cap.max) * 100) : 0,
|
|
396
|
-
earningsToday: '0',
|
|
397
|
-
tokensToday: 0,
|
|
398
|
-
uptime: formatUptime(),
|
|
399
|
-
updatedAt: now,
|
|
400
|
-
proxyPort: null,
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
async function writeDaemonState(): Promise<void> {
|
|
405
|
-
if (stateWriteInFlight) {
|
|
406
|
-
stateWritePending = true
|
|
407
|
-
return
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
stateWriteInFlight = true
|
|
411
|
-
try {
|
|
412
|
-
await writeFile(STATE_FILE, JSON.stringify(buildDaemonState(), null, 2))
|
|
413
|
-
} finally {
|
|
414
|
-
stateWriteInFlight = false
|
|
415
|
-
if (stateWritePending) {
|
|
416
|
-
stateWritePending = false
|
|
417
|
-
await writeDaemonState()
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
function scheduleDaemonStateWrite(): void {
|
|
423
|
-
void writeDaemonState().catch(() => {})
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
await writeDaemonState()
|
|
427
|
-
node.on('connection', scheduleDaemonStateWrite)
|
|
428
|
-
node.on('session:updated', scheduleDaemonStateWrite)
|
|
429
|
-
node.on('session:finalized', scheduleDaemonStateWrite)
|
|
430
|
-
|
|
431
|
-
// Refresh state file every 1s so the desktop dashboard reflects live counters.
|
|
432
|
-
const stateInterval = setInterval(async () => {
|
|
433
|
-
await writeDaemonState().catch(() => {})
|
|
434
|
-
}, 1_000)
|
|
435
|
-
|
|
436
|
-
setupShutdownHandler(async () => {
|
|
437
|
-
clearInterval(stateInterval)
|
|
438
|
-
node.off('connection', scheduleDaemonStateWrite)
|
|
439
|
-
node.off('session:updated', scheduleDaemonStateWrite)
|
|
440
|
-
node.off('session:finalized', scheduleDaemonStateWrite)
|
|
441
|
-
nodeSpinner.start('Shutting down seeding daemon...')
|
|
442
|
-
await node.stop()
|
|
443
|
-
await unlink(STATE_FILE).catch(() => {})
|
|
444
|
-
nodeSpinner.succeed('Seeding daemon stopped. Sessions finalized.')
|
|
445
|
-
})
|
|
446
|
-
})
|
|
447
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import type { Command } from 'commander';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import Table from 'cli-table3';
|
|
4
|
-
import { getGlobalOptions } from './types.js';
|
|
5
|
-
import { loadConfig } from '../../config/loader.js';
|
|
6
|
-
import { resolveEffectiveSellerConfig } from '../../config/effective.js';
|
|
7
|
-
import { getNodeStatus } from '../../status/node-status.js';
|
|
8
|
-
import type { NodeStatus } from '../../status/node-status.js';
|
|
9
|
-
import { formatEarnings, formatTokens } from '../formatters.js';
|
|
10
|
-
|
|
11
|
-
/** Possible node states */
|
|
12
|
-
export type NodeState = 'seeding' | 'connected' | 'idle';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Register the `antseed status` command on the Commander program.
|
|
16
|
-
*/
|
|
17
|
-
export function registerStatusCommand(program: Command): void {
|
|
18
|
-
program
|
|
19
|
-
.command('status')
|
|
20
|
-
.description('Show current Antseed node status')
|
|
21
|
-
.option('--json', 'output as JSON', false)
|
|
22
|
-
.action(async (options) => {
|
|
23
|
-
try {
|
|
24
|
-
const globalOpts = getGlobalOptions(program);
|
|
25
|
-
const config = await loadConfig(globalOpts.config);
|
|
26
|
-
const effectiveSeller = resolveEffectiveSellerConfig({ config });
|
|
27
|
-
const status: NodeStatus = await getNodeStatus(config);
|
|
28
|
-
|
|
29
|
-
if (options.json) {
|
|
30
|
-
console.log(JSON.stringify(status, null, 2));
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// State badge
|
|
35
|
-
const stateColors: Record<NodeState, (s: string) => string> = {
|
|
36
|
-
seeding: chalk.green,
|
|
37
|
-
connected: chalk.cyan,
|
|
38
|
-
idle: chalk.gray,
|
|
39
|
-
};
|
|
40
|
-
const colorFn = stateColors[status.state] ?? chalk.white;
|
|
41
|
-
console.log(chalk.bold('Status: ') + colorFn(status.state.toUpperCase()));
|
|
42
|
-
console.log('');
|
|
43
|
-
|
|
44
|
-
// Summary table
|
|
45
|
-
const table = new Table({
|
|
46
|
-
head: [chalk.bold('Metric'), chalk.bold('Value')],
|
|
47
|
-
colWidths: [25, 35],
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
table.push(
|
|
51
|
-
['Peers connected', chalk.cyan(String(status.peerCount))],
|
|
52
|
-
['Earnings today', chalk.green(formatEarnings(status.earningsToday))],
|
|
53
|
-
['Tokens today', formatTokens(status.tokensToday)],
|
|
54
|
-
['Active sessions', String(status.activeSessions)],
|
|
55
|
-
['Uptime', status.uptime],
|
|
56
|
-
['Wallet address', status.walletAddress ?? chalk.dim('not configured')],
|
|
57
|
-
['Proxy port', status.proxyPort ? String(status.proxyPort) : chalk.dim('n/a')],
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
if (status.state === 'seeding') {
|
|
61
|
-
table.push([
|
|
62
|
-
'Seller pricing defaults (USD/1M in/out)',
|
|
63
|
-
`${effectiveSeller.pricing.defaults.inputUsdPerMillion} / ${effectiveSeller.pricing.defaults.outputUsdPerMillion}`,
|
|
64
|
-
]);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
console.log(table.toString());
|
|
68
|
-
} catch (err) {
|
|
69
|
-
console.error(chalk.red(`Error: ${(err as Error).message}`));
|
|
70
|
-
process.exitCode = 1;
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
}
|