@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,56 +0,0 @@
|
|
|
1
|
-
import type { Command } from 'commander';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { join, resolve } from 'node:path';
|
|
4
|
-
|
|
5
|
-
/** All command registration functions follow this signature */
|
|
6
|
-
export type RegisterCommandFn = (program: Command) => void;
|
|
7
|
-
|
|
8
|
-
/** Global CLI options available to all commands */
|
|
9
|
-
export interface GlobalOptions {
|
|
10
|
-
config: string;
|
|
11
|
-
dataDir: string;
|
|
12
|
-
verbose: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const NEW_HOME_DIR = join(homedir(), '.antseed');
|
|
16
|
-
const NEW_CONFIG_PATH = join(NEW_HOME_DIR, 'config.json');
|
|
17
|
-
|
|
18
|
-
function resolvePathWithHome(pathLike: string, fallbackAbsolutePath: string): string {
|
|
19
|
-
const raw = typeof pathLike === 'string' ? pathLike.trim() : '';
|
|
20
|
-
if (raw.length === 0) {
|
|
21
|
-
return fallbackAbsolutePath;
|
|
22
|
-
}
|
|
23
|
-
if (raw === '~') {
|
|
24
|
-
return homedir();
|
|
25
|
-
}
|
|
26
|
-
if (raw.startsWith('~/')) {
|
|
27
|
-
return join(homedir(), raw.slice(2));
|
|
28
|
-
}
|
|
29
|
-
return resolve(raw);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function isTruthyEnv(value: string | undefined): boolean {
|
|
33
|
-
if (!value) return false;
|
|
34
|
-
const normalized = value.trim().toLowerCase();
|
|
35
|
-
return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Extract global options from the Commander program instance.
|
|
40
|
-
*/
|
|
41
|
-
export function getGlobalOptions(program: Command): GlobalOptions {
|
|
42
|
-
const opts = program.opts();
|
|
43
|
-
const verbose = opts['verbose'] ?? false;
|
|
44
|
-
if (verbose || isTruthyEnv(process.env['ANTSEED_DEBUG'])) {
|
|
45
|
-
process.env['ANTSEED_DEBUG'] = '1';
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const requestedConfig = resolvePathWithHome(opts['config'] ?? '~/.antseed/config.json', NEW_CONFIG_PATH);
|
|
49
|
-
const requestedDataDir = resolvePathWithHome(opts['dataDir'] ?? '~/.antseed', NEW_HOME_DIR);
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
config: requestedConfig,
|
|
53
|
-
dataDir: requestedDataDir,
|
|
54
|
-
verbose,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import type { Command } from 'commander';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import ora from 'ora';
|
|
4
|
-
import { getGlobalOptions } from './types.js';
|
|
5
|
-
import { loadConfig } from '../../config/loader.js';
|
|
6
|
-
import {
|
|
7
|
-
loadOrCreateIdentity,
|
|
8
|
-
BaseEscrowClient,
|
|
9
|
-
identityToEvmWallet,
|
|
10
|
-
identityToEvmAddress,
|
|
11
|
-
} from '@antseed/node';
|
|
12
|
-
|
|
13
|
-
export function registerWithdrawCommand(program: Command): void {
|
|
14
|
-
program
|
|
15
|
-
.command('withdraw <amount>')
|
|
16
|
-
.description('Withdraw USDC from the escrow contract (amount in human-readable USDC, e.g. "5" = 5 USDC)')
|
|
17
|
-
.action(async (amount: string) => {
|
|
18
|
-
const globalOpts = getGlobalOptions(program);
|
|
19
|
-
const config = await loadConfig(globalOpts.config);
|
|
20
|
-
|
|
21
|
-
const payments = config.payments;
|
|
22
|
-
if (!payments?.crypto) {
|
|
23
|
-
console.error(chalk.red('Error: No crypto payment configuration found.'));
|
|
24
|
-
console.error(chalk.dim('Configure payments.crypto in your config file or run: antseed init'));
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const amountFloat = parseFloat(amount);
|
|
29
|
-
if (isNaN(amountFloat) || amountFloat <= 0) {
|
|
30
|
-
console.error(chalk.red('Error: Amount must be a positive number.'));
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Convert human-readable USDC to base units (6 decimals)
|
|
35
|
-
const amountBaseUnits = BigInt(Math.round(amountFloat * 1_000_000));
|
|
36
|
-
|
|
37
|
-
const identity = await loadOrCreateIdentity(globalOpts.dataDir);
|
|
38
|
-
const wallet = identityToEvmWallet(identity);
|
|
39
|
-
const address = identityToEvmAddress(identity);
|
|
40
|
-
|
|
41
|
-
const escrowClient = new BaseEscrowClient({
|
|
42
|
-
rpcUrl: payments.crypto.rpcUrl,
|
|
43
|
-
contractAddress: payments.crypto.escrowContractAddress,
|
|
44
|
-
usdcAddress: payments.crypto.usdcContractAddress,
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
console.log(chalk.dim(`Wallet: ${address}`));
|
|
48
|
-
console.log(chalk.dim(`Amount: ${amountFloat} USDC (${amountBaseUnits} base units)`));
|
|
49
|
-
|
|
50
|
-
const spinner = ora('Withdrawing USDC from escrow...').start();
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
const txHash = await escrowClient.withdraw(wallet, amountBaseUnits);
|
|
54
|
-
spinner.succeed(chalk.green(`Withdrew ${amountFloat} USDC from escrow`));
|
|
55
|
-
console.log(chalk.dim(`Transaction: ${txHash}`));
|
|
56
|
-
} catch (err) {
|
|
57
|
-
spinner.fail(chalk.red(`Withdrawal failed: ${(err as Error).message}`));
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
}
|
package/src/cli/formatters.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Format a USD earnings string with a dollar sign and green color.
|
|
5
|
-
* @param amountUSD - Amount as string (e.g., "1.234567")
|
|
6
|
-
* @returns Formatted string like "$1.23"
|
|
7
|
-
*/
|
|
8
|
-
export function formatEarnings(amountUSD: string): string {
|
|
9
|
-
const num = parseFloat(amountUSD);
|
|
10
|
-
return chalk.green(`$${num.toFixed(2)}`);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Format a token count with K/M suffix.
|
|
15
|
-
* @param tokens - Number of tokens
|
|
16
|
-
* @returns Formatted string like "1.5K" or "2.3M"
|
|
17
|
-
*/
|
|
18
|
-
export function formatTokens(tokens: number): string {
|
|
19
|
-
if (tokens >= 1_000_000) {
|
|
20
|
-
return `${(tokens / 1_000_000).toFixed(1)}M`;
|
|
21
|
-
}
|
|
22
|
-
if (tokens >= 1_000) {
|
|
23
|
-
return `${(tokens / 1_000).toFixed(1)}K`;
|
|
24
|
-
}
|
|
25
|
-
return String(tokens);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Format a capacity usage percentage with color coding.
|
|
30
|
-
* Green < 50%, Yellow 50-80%, Red > 80%
|
|
31
|
-
*/
|
|
32
|
-
export function formatCapacity(percent: number): string {
|
|
33
|
-
const label = `${percent.toFixed(0)}%`;
|
|
34
|
-
if (percent < 50) return chalk.green(label);
|
|
35
|
-
if (percent < 80) return chalk.yellow(label);
|
|
36
|
-
return chalk.red(label);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Format a duration in milliseconds to a human-readable string.
|
|
41
|
-
* @param ms - Duration in milliseconds
|
|
42
|
-
* @returns String like "2h 15m" or "45s" or "3d 1h"
|
|
43
|
-
*/
|
|
44
|
-
export function formatDuration(ms: number): string {
|
|
45
|
-
const seconds = Math.floor(ms / 1000);
|
|
46
|
-
if (seconds < 60) return `${seconds}s`;
|
|
47
|
-
const minutes = Math.floor(seconds / 60);
|
|
48
|
-
if (minutes < 60) return `${minutes}m ${seconds % 60}s`;
|
|
49
|
-
const hours = Math.floor(minutes / 60);
|
|
50
|
-
if (hours < 24) return `${hours}h ${minutes % 60}m`;
|
|
51
|
-
const days = Math.floor(hours / 24);
|
|
52
|
-
return `${days}d ${hours % 24}h`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Format a reputation score (0-1) with color coding.
|
|
57
|
-
* Green >= 0.8, Yellow >= 0.5, Red < 0.5
|
|
58
|
-
*/
|
|
59
|
-
export function formatReputation(score: number): string {
|
|
60
|
-
const label = `${(score * 100).toFixed(0)}%`;
|
|
61
|
-
if (score >= 0.8) return chalk.green(label);
|
|
62
|
-
if (score >= 0.5) return chalk.yellow(label);
|
|
63
|
-
return chalk.red(label);
|
|
64
|
-
}
|
package/src/cli/index.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { Command } from 'commander';
|
|
4
|
-
import { loadEnvFromFiles } from '../env/load-env.js';
|
|
5
|
-
import { registerSeedCommand } from './commands/seed.js';
|
|
6
|
-
import { registerConnectCommand } from './commands/connect.js';
|
|
7
|
-
import { registerStatusCommand } from './commands/status.js';
|
|
8
|
-
import { registerConfigCommand } from './commands/config.js';
|
|
9
|
-
import { registerDashboardCommand } from './commands/dashboard.js';
|
|
10
|
-
import { registerDevCommand } from './commands/dev.js';
|
|
11
|
-
import { registerBrowseCommand } from './commands/browse.js';
|
|
12
|
-
import { registerInitCommand } from './commands/init.js';
|
|
13
|
-
import { registerPluginCommand } from './commands/plugin.js';
|
|
14
|
-
import { registerProfileCommand, registerPeerCommand } from './commands/profile.js';
|
|
15
|
-
import { registerDepositCommand } from './commands/deposit.js';
|
|
16
|
-
import { registerWithdrawCommand } from './commands/withdraw.js';
|
|
17
|
-
import { registerBalanceCommand } from './commands/balance.js';
|
|
18
|
-
|
|
19
|
-
loadEnvFromFiles();
|
|
20
|
-
|
|
21
|
-
const program = new Command();
|
|
22
|
-
|
|
23
|
-
program
|
|
24
|
-
.name('antseed')
|
|
25
|
-
.description('P2P marketplace for reselling idle LLM plan capacity')
|
|
26
|
-
.version('0.1.0')
|
|
27
|
-
.option('-c, --config <path>', 'path to config file', '~/.antseed/config.json')
|
|
28
|
-
.option('--data-dir <path>', 'path to node identity/state directory', '~/.antseed')
|
|
29
|
-
.option('-v, --verbose', 'enable verbose logging', false);
|
|
30
|
-
|
|
31
|
-
registerSeedCommand(program);
|
|
32
|
-
registerConnectCommand(program);
|
|
33
|
-
registerStatusCommand(program);
|
|
34
|
-
registerConfigCommand(program);
|
|
35
|
-
registerDashboardCommand(program);
|
|
36
|
-
registerDevCommand(program);
|
|
37
|
-
registerBrowseCommand(program);
|
|
38
|
-
registerInitCommand(program);
|
|
39
|
-
registerPluginCommand(program);
|
|
40
|
-
registerProfileCommand(program);
|
|
41
|
-
registerPeerCommand(program);
|
|
42
|
-
registerDepositCommand(program);
|
|
43
|
-
registerWithdrawCommand(program);
|
|
44
|
-
registerBalanceCommand(program);
|
|
45
|
-
|
|
46
|
-
program.parse(process.argv);
|
package/src/cli/shutdown.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
type ShutdownCallback = () => Promise<void>;
|
|
2
|
-
|
|
3
|
-
let shutdownCallbacks: ShutdownCallback[] = [];
|
|
4
|
-
let isShuttingDown = false;
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Register a callback to be called on shutdown (SIGINT/SIGTERM).
|
|
8
|
-
* Multiple callbacks are called in LIFO order (last registered = first called).
|
|
9
|
-
* The process exits after all callbacks complete.
|
|
10
|
-
*
|
|
11
|
-
* @param callback - Async function to run during shutdown
|
|
12
|
-
*/
|
|
13
|
-
export function setupShutdownHandler(callback: ShutdownCallback): void {
|
|
14
|
-
shutdownCallbacks.push(callback);
|
|
15
|
-
|
|
16
|
-
// Only register signal handlers once
|
|
17
|
-
if (shutdownCallbacks.length === 1) {
|
|
18
|
-
const handler = async () => {
|
|
19
|
-
if (isShuttingDown) return; // Prevent double-shutdown
|
|
20
|
-
isShuttingDown = true;
|
|
21
|
-
|
|
22
|
-
// Execute callbacks in LIFO order
|
|
23
|
-
const callbacks = [...shutdownCallbacks].reverse();
|
|
24
|
-
for (const cb of callbacks) {
|
|
25
|
-
try {
|
|
26
|
-
await cb();
|
|
27
|
-
} catch (err) {
|
|
28
|
-
console.error('Shutdown error:', (err as Error).message);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
process.exit(0);
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
process.on('SIGINT', handler);
|
|
36
|
-
process.on('SIGTERM', handler);
|
|
37
|
-
}
|
|
38
|
-
}
|
package/src/config/defaults.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import type { AntseedConfig } from './types.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Create a default Antseed configuration with sensible defaults.
|
|
5
|
-
*/
|
|
6
|
-
export function createDefaultConfig(): AntseedConfig {
|
|
7
|
-
return {
|
|
8
|
-
identity: {
|
|
9
|
-
displayName: 'Antseed Node',
|
|
10
|
-
},
|
|
11
|
-
providers: [],
|
|
12
|
-
seller: {
|
|
13
|
-
reserveFloor: 10,
|
|
14
|
-
maxConcurrentBuyers: 5,
|
|
15
|
-
enabledProviders: [],
|
|
16
|
-
pricing: {
|
|
17
|
-
defaults: {
|
|
18
|
-
inputUsdPerMillion: 10,
|
|
19
|
-
outputUsdPerMillion: 10,
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
buyer: {
|
|
24
|
-
preferredProviders: ['anthropic', 'openai', 'claude-code', 'claude-oauth', 'openrouter', 'local-llm'],
|
|
25
|
-
maxPricing: {
|
|
26
|
-
defaults: {
|
|
27
|
-
inputUsdPerMillion: 100,
|
|
28
|
-
outputUsdPerMillion: 100,
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
minPeerReputation: 50,
|
|
32
|
-
proxyPort: 8377,
|
|
33
|
-
},
|
|
34
|
-
payments: {
|
|
35
|
-
preferredMethod: 'crypto',
|
|
36
|
-
platformFeeRate: 0.05,
|
|
37
|
-
crypto: {
|
|
38
|
-
chainId: 'base-local',
|
|
39
|
-
rpcUrl: 'http://127.0.0.1:8545',
|
|
40
|
-
escrowContractAddress: '0x5FbDB2315678afecb367f032d93F642f64180aa3',
|
|
41
|
-
usdcContractAddress: '0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512',
|
|
42
|
-
defaultLockAmountUSDC: '1',
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
network: {
|
|
46
|
-
bootstrapNodes: [],
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict';
|
|
2
|
-
import test from 'node:test';
|
|
3
|
-
import { createDefaultConfig } from './defaults.js';
|
|
4
|
-
import {
|
|
5
|
-
resolveEffectiveBuyerConfig,
|
|
6
|
-
resolveEffectiveRoleConfig,
|
|
7
|
-
resolveEffectiveSellerConfig,
|
|
8
|
-
} from './effective.js';
|
|
9
|
-
|
|
10
|
-
test('effective seller config precedence is flags > env > config > defaults', () => {
|
|
11
|
-
const config = createDefaultConfig();
|
|
12
|
-
config.seller.pricing.defaults.inputUsdPerMillion = 10;
|
|
13
|
-
config.seller.pricing.defaults.outputUsdPerMillion = 20;
|
|
14
|
-
|
|
15
|
-
const env = {
|
|
16
|
-
ANTSEED_SELLER_INPUT_USD_PER_MILLION: '30',
|
|
17
|
-
ANTSEED_SELLER_OUTPUT_USD_PER_MILLION: '40',
|
|
18
|
-
} as NodeJS.ProcessEnv;
|
|
19
|
-
|
|
20
|
-
const effective = resolveEffectiveSellerConfig({
|
|
21
|
-
config,
|
|
22
|
-
env,
|
|
23
|
-
sellerOverrides: {
|
|
24
|
-
inputUsdPerMillion: 50,
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
assert.equal(effective.pricing.defaults.inputUsdPerMillion, 50);
|
|
29
|
-
assert.equal(effective.pricing.defaults.outputUsdPerMillion, 40);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test('effective buyer config precedence is flags > env > config > defaults', () => {
|
|
33
|
-
const config = createDefaultConfig();
|
|
34
|
-
config.buyer.minPeerReputation = 25;
|
|
35
|
-
config.buyer.maxPricing.defaults.inputUsdPerMillion = 70;
|
|
36
|
-
config.buyer.maxPricing.defaults.outputUsdPerMillion = 80;
|
|
37
|
-
|
|
38
|
-
const env = {
|
|
39
|
-
ANTSEED_BUYER_MIN_REPUTATION: '45',
|
|
40
|
-
ANTSEED_BUYER_PREFERRED_PROVIDERS: 'openai,anthropic',
|
|
41
|
-
ANTSEED_BUYER_MAX_INPUT_USD_PER_MILLION: '90',
|
|
42
|
-
ANTSEED_BUYER_MAX_OUTPUT_USD_PER_MILLION: '95',
|
|
43
|
-
} as NodeJS.ProcessEnv;
|
|
44
|
-
|
|
45
|
-
const effective = resolveEffectiveBuyerConfig({
|
|
46
|
-
config,
|
|
47
|
-
env,
|
|
48
|
-
buyerOverrides: {
|
|
49
|
-
minPeerReputation: 55,
|
|
50
|
-
preferredProviders: ['anthropic', 'openai'],
|
|
51
|
-
maxOutputUsdPerMillion: 99,
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
assert.equal(effective.minPeerReputation, 55);
|
|
56
|
-
assert.deepEqual(effective.preferredProviders, ['anthropic', 'openai']);
|
|
57
|
-
assert.equal(effective.maxPricing.defaults.inputUsdPerMillion, 90);
|
|
58
|
-
assert.equal(effective.maxPricing.defaults.outputUsdPerMillion, 99);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('effective config resolution does not mutate loaded config', () => {
|
|
62
|
-
const config = createDefaultConfig();
|
|
63
|
-
const original = JSON.parse(JSON.stringify(config));
|
|
64
|
-
|
|
65
|
-
const env = {
|
|
66
|
-
ANTSEED_BUYER_MAX_INPUT_USD_PER_MILLION: '123',
|
|
67
|
-
} as NodeJS.ProcessEnv;
|
|
68
|
-
|
|
69
|
-
const effective = resolveEffectiveRoleConfig({
|
|
70
|
-
config,
|
|
71
|
-
env,
|
|
72
|
-
sellerOverrides: {
|
|
73
|
-
outputUsdPerMillion: 44,
|
|
74
|
-
},
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
assert.equal(effective.buyer.maxPricing.defaults.inputUsdPerMillion, 123);
|
|
78
|
-
assert.equal(effective.seller.pricing.defaults.outputUsdPerMillion, 44);
|
|
79
|
-
assert.deepEqual(config, original);
|
|
80
|
-
});
|
package/src/config/effective.ts
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import type { BuyerCLIConfig, AntseedConfig, SellerCLIConfig } from './types.js';
|
|
2
|
-
|
|
3
|
-
export interface SellerRuntimeOverrides {
|
|
4
|
-
reserveFloor?: number;
|
|
5
|
-
inputUsdPerMillion?: number;
|
|
6
|
-
outputUsdPerMillion?: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface BuyerRuntimeOverrides {
|
|
10
|
-
proxyPort?: number;
|
|
11
|
-
minPeerReputation?: number;
|
|
12
|
-
preferredProviders?: string[];
|
|
13
|
-
maxInputUsdPerMillion?: number;
|
|
14
|
-
maxOutputUsdPerMillion?: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface ResolveEffectiveConfigInput {
|
|
18
|
-
config: AntseedConfig;
|
|
19
|
-
env?: NodeJS.ProcessEnv;
|
|
20
|
-
sellerOverrides?: SellerRuntimeOverrides;
|
|
21
|
-
buyerOverrides?: BuyerRuntimeOverrides;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function parseEnvNumber(env: NodeJS.ProcessEnv, key: string): number | undefined {
|
|
25
|
-
const raw = env[key];
|
|
26
|
-
if (raw === undefined) return undefined;
|
|
27
|
-
const parsed = Number(raw);
|
|
28
|
-
return Number.isFinite(parsed) ? parsed : undefined;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function parseEnvProviders(env: NodeJS.ProcessEnv, key: string): string[] | undefined {
|
|
32
|
-
const raw = env[key];
|
|
33
|
-
if (!raw) return undefined;
|
|
34
|
-
const list = raw
|
|
35
|
-
.split(',')
|
|
36
|
-
.map((entry) => entry.trim())
|
|
37
|
-
.filter((entry) => entry.length > 0);
|
|
38
|
-
return list.length > 0 ? list : undefined;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function resolveEffectiveSellerConfig(input: ResolveEffectiveConfigInput): SellerCLIConfig {
|
|
42
|
-
const env = input.env ?? process.env;
|
|
43
|
-
const seller = structuredClone(input.config.seller);
|
|
44
|
-
|
|
45
|
-
const envInputUsdPerMillion = parseEnvNumber(env, 'ANTSEED_SELLER_INPUT_USD_PER_MILLION');
|
|
46
|
-
const envOutputUsdPerMillion = parseEnvNumber(env, 'ANTSEED_SELLER_OUTPUT_USD_PER_MILLION');
|
|
47
|
-
|
|
48
|
-
if (envInputUsdPerMillion !== undefined) {
|
|
49
|
-
seller.pricing.defaults.inputUsdPerMillion = envInputUsdPerMillion;
|
|
50
|
-
}
|
|
51
|
-
if (envOutputUsdPerMillion !== undefined) {
|
|
52
|
-
seller.pricing.defaults.outputUsdPerMillion = envOutputUsdPerMillion;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const overrides = input.sellerOverrides;
|
|
56
|
-
if (overrides?.reserveFloor !== undefined) {
|
|
57
|
-
seller.reserveFloor = overrides.reserveFloor;
|
|
58
|
-
}
|
|
59
|
-
if (overrides?.inputUsdPerMillion !== undefined) {
|
|
60
|
-
seller.pricing.defaults.inputUsdPerMillion = overrides.inputUsdPerMillion;
|
|
61
|
-
}
|
|
62
|
-
if (overrides?.outputUsdPerMillion !== undefined) {
|
|
63
|
-
seller.pricing.defaults.outputUsdPerMillion = overrides.outputUsdPerMillion;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return seller;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function resolveEffectiveBuyerConfig(input: ResolveEffectiveConfigInput): BuyerCLIConfig {
|
|
70
|
-
const env = input.env ?? process.env;
|
|
71
|
-
const buyer = structuredClone(input.config.buyer);
|
|
72
|
-
|
|
73
|
-
const envMinReputation = parseEnvNumber(env, 'ANTSEED_BUYER_MIN_REPUTATION');
|
|
74
|
-
const envPreferredProviders = parseEnvProviders(env, 'ANTSEED_BUYER_PREFERRED_PROVIDERS');
|
|
75
|
-
const envMaxInputUsdPerMillion = parseEnvNumber(env, 'ANTSEED_BUYER_MAX_INPUT_USD_PER_MILLION');
|
|
76
|
-
const envMaxOutputUsdPerMillion = parseEnvNumber(env, 'ANTSEED_BUYER_MAX_OUTPUT_USD_PER_MILLION');
|
|
77
|
-
|
|
78
|
-
if (envMinReputation !== undefined) {
|
|
79
|
-
buyer.minPeerReputation = envMinReputation;
|
|
80
|
-
}
|
|
81
|
-
if (envPreferredProviders) {
|
|
82
|
-
buyer.preferredProviders = envPreferredProviders;
|
|
83
|
-
}
|
|
84
|
-
if (envMaxInputUsdPerMillion !== undefined) {
|
|
85
|
-
buyer.maxPricing.defaults.inputUsdPerMillion = envMaxInputUsdPerMillion;
|
|
86
|
-
}
|
|
87
|
-
if (envMaxOutputUsdPerMillion !== undefined) {
|
|
88
|
-
buyer.maxPricing.defaults.outputUsdPerMillion = envMaxOutputUsdPerMillion;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const overrides = input.buyerOverrides;
|
|
92
|
-
if (overrides?.proxyPort !== undefined) {
|
|
93
|
-
buyer.proxyPort = overrides.proxyPort;
|
|
94
|
-
}
|
|
95
|
-
if (overrides?.minPeerReputation !== undefined) {
|
|
96
|
-
buyer.minPeerReputation = overrides.minPeerReputation;
|
|
97
|
-
}
|
|
98
|
-
if (overrides?.preferredProviders && overrides.preferredProviders.length > 0) {
|
|
99
|
-
buyer.preferredProviders = [...overrides.preferredProviders];
|
|
100
|
-
}
|
|
101
|
-
if (overrides?.maxInputUsdPerMillion !== undefined) {
|
|
102
|
-
buyer.maxPricing.defaults.inputUsdPerMillion = overrides.maxInputUsdPerMillion;
|
|
103
|
-
}
|
|
104
|
-
if (overrides?.maxOutputUsdPerMillion !== undefined) {
|
|
105
|
-
buyer.maxPricing.defaults.outputUsdPerMillion = overrides.maxOutputUsdPerMillion;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return buyer;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export function resolveEffectiveRoleConfig(input: ResolveEffectiveConfigInput): {
|
|
112
|
-
seller: SellerCLIConfig;
|
|
113
|
-
buyer: BuyerCLIConfig;
|
|
114
|
-
} {
|
|
115
|
-
return {
|
|
116
|
-
seller: resolveEffectiveSellerConfig(input),
|
|
117
|
-
buyer: resolveEffectiveBuyerConfig(input),
|
|
118
|
-
};
|
|
119
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict';
|
|
2
|
-
import test from 'node:test';
|
|
3
|
-
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { tmpdir } from 'node:os';
|
|
6
|
-
import { loadConfig } from './loader.js';
|
|
7
|
-
|
|
8
|
-
async function withTempConfig(contents: string, fn: (configPath: string) => Promise<void>): Promise<void> {
|
|
9
|
-
const dir = await mkdtemp(join(tmpdir(), 'antseed-cli-config-'));
|
|
10
|
-
const configPath = join(dir, 'config.json');
|
|
11
|
-
try {
|
|
12
|
-
await writeFile(configPath, contents, 'utf-8');
|
|
13
|
-
await fn(configPath);
|
|
14
|
-
} finally {
|
|
15
|
-
await rm(dir, { recursive: true, force: true });
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
test('loadConfig deep-merges nested hierarchical pricing without dropping defaults', async () => {
|
|
20
|
-
await withTempConfig(
|
|
21
|
-
JSON.stringify({
|
|
22
|
-
seller: {
|
|
23
|
-
pricing: {
|
|
24
|
-
providers: {
|
|
25
|
-
anthropic: {
|
|
26
|
-
models: {
|
|
27
|
-
'claude-sonnet-4-5-20250929': {
|
|
28
|
-
inputUsdPerMillion: 12,
|
|
29
|
-
outputUsdPerMillion: 18,
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
buyer: {
|
|
37
|
-
maxPricing: {
|
|
38
|
-
providers: {
|
|
39
|
-
openai: {
|
|
40
|
-
defaults: {
|
|
41
|
-
inputUsdPerMillion: 55,
|
|
42
|
-
outputUsdPerMillion: 77,
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
}),
|
|
49
|
-
async (configPath) => {
|
|
50
|
-
const config = await loadConfig(configPath);
|
|
51
|
-
|
|
52
|
-
assert.equal(config.seller.pricing.defaults.inputUsdPerMillion, 10);
|
|
53
|
-
assert.equal(config.seller.pricing.defaults.outputUsdPerMillion, 10);
|
|
54
|
-
assert.equal(
|
|
55
|
-
config.seller.pricing.providers?.anthropic?.models?.['claude-sonnet-4-5-20250929']?.inputUsdPerMillion,
|
|
56
|
-
12
|
|
57
|
-
);
|
|
58
|
-
assert.equal(
|
|
59
|
-
config.seller.pricing.providers?.anthropic?.models?.['claude-sonnet-4-5-20250929']?.outputUsdPerMillion,
|
|
60
|
-
18
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
assert.equal(config.buyer.maxPricing.defaults.inputUsdPerMillion, 100);
|
|
64
|
-
assert.equal(config.buyer.maxPricing.defaults.outputUsdPerMillion, 100);
|
|
65
|
-
assert.equal(config.buyer.maxPricing.providers?.openai?.defaults?.inputUsdPerMillion, 55);
|
|
66
|
-
assert.equal(config.buyer.maxPricing.providers?.openai?.defaults?.outputUsdPerMillion, 77);
|
|
67
|
-
}
|
|
68
|
-
);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('loadConfig throws explicit validation error for incomplete model pricing', async () => {
|
|
72
|
-
await withTempConfig(
|
|
73
|
-
JSON.stringify({
|
|
74
|
-
seller: {
|
|
75
|
-
pricing: {
|
|
76
|
-
providers: {
|
|
77
|
-
anthropic: {
|
|
78
|
-
models: {
|
|
79
|
-
broken: {
|
|
80
|
-
inputUsdPerMillion: 12,
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
}),
|
|
88
|
-
async (configPath) => {
|
|
89
|
-
await assert.rejects(
|
|
90
|
-
async () => loadConfig(configPath),
|
|
91
|
-
/seller\.pricing\.providers\.anthropic\.models\.broken\.outputUsdPerMillion/
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
);
|
|
95
|
-
});
|