@antseed/cli 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/.env.example +15 -0
- package/README.md +169 -0
- package/dist/cli/commands/balance.d.ts +3 -0
- package/dist/cli/commands/balance.d.ts.map +1 -0
- package/dist/cli/commands/balance.js +64 -0
- package/dist/cli/commands/balance.js.map +1 -0
- package/dist/cli/commands/browse.d.ts +7 -0
- package/dist/cli/commands/browse.d.ts.map +1 -0
- package/dist/cli/commands/browse.js +100 -0
- package/dist/cli/commands/browse.js.map +1 -0
- package/dist/cli/commands/config.d.ts +20 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +239 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/connect.d.ts +14 -0
- package/dist/cli/commands/connect.d.ts.map +1 -0
- package/dist/cli/commands/connect.js +298 -0
- package/dist/cli/commands/connect.js.map +1 -0
- package/dist/cli/commands/connect.test.d.ts +2 -0
- package/dist/cli/commands/connect.test.d.ts.map +1 -0
- package/dist/cli/commands/connect.test.js +54 -0
- package/dist/cli/commands/connect.test.js.map +1 -0
- package/dist/cli/commands/dashboard.d.ts +6 -0
- package/dist/cli/commands/dashboard.d.ts.map +1 -0
- package/dist/cli/commands/dashboard.js +48 -0
- package/dist/cli/commands/dashboard.js.map +1 -0
- package/dist/cli/commands/deposit.d.ts +3 -0
- package/dist/cli/commands/deposit.d.ts.map +1 -0
- package/dist/cli/commands/deposit.js +48 -0
- package/dist/cli/commands/deposit.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +3 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/dev.js +94 -0
- package/dist/cli/commands/dev.js.map +1 -0
- package/dist/cli/commands/init.d.ts +3 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +91 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/plugin-create.d.ts +11 -0
- package/dist/cli/commands/plugin-create.d.ts.map +1 -0
- package/dist/cli/commands/plugin-create.js +201 -0
- package/dist/cli/commands/plugin-create.js.map +1 -0
- package/dist/cli/commands/plugin-create.test.d.ts +2 -0
- package/dist/cli/commands/plugin-create.test.d.ts.map +1 -0
- package/dist/cli/commands/plugin-create.test.js +53 -0
- package/dist/cli/commands/plugin-create.test.js.map +1 -0
- package/dist/cli/commands/plugin.d.ts +3 -0
- package/dist/cli/commands/plugin.d.ts.map +1 -0
- package/dist/cli/commands/plugin.js +279 -0
- package/dist/cli/commands/plugin.js.map +1 -0
- package/dist/cli/commands/plugin.test.d.ts +2 -0
- package/dist/cli/commands/plugin.test.d.ts.map +1 -0
- package/dist/cli/commands/plugin.test.js +53 -0
- package/dist/cli/commands/plugin.test.js.map +1 -0
- package/dist/cli/commands/profile.d.ts +10 -0
- package/dist/cli/commands/profile.d.ts.map +1 -0
- package/dist/cli/commands/profile.js +89 -0
- package/dist/cli/commands/profile.js.map +1 -0
- package/dist/cli/commands/seed.d.ts +11 -0
- package/dist/cli/commands/seed.d.ts.map +1 -0
- package/dist/cli/commands/seed.js +397 -0
- package/dist/cli/commands/seed.js.map +1 -0
- package/dist/cli/commands/seed.test.d.ts +2 -0
- package/dist/cli/commands/seed.test.d.ts.map +1 -0
- package/dist/cli/commands/seed.test.js +57 -0
- package/dist/cli/commands/seed.test.js.map +1 -0
- package/dist/cli/commands/status.d.ts +8 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +55 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/types.d.ts +14 -0
- package/dist/cli/commands/types.d.ts.map +1 -0
- package/dist/cli/commands/types.js +41 -0
- package/dist/cli/commands/types.js.map +1 -0
- package/dist/cli/commands/withdraw.d.ts +3 -0
- package/dist/cli/commands/withdraw.d.ts.map +1 -0
- package/dist/cli/commands/withdraw.js +48 -0
- package/dist/cli/commands/withdraw.js.map +1 -0
- package/dist/cli/formatters.d.ts +29 -0
- package/dist/cli/formatters.d.ts.map +1 -0
- package/dist/cli/formatters.js +67 -0
- package/dist/cli/formatters.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +41 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/shutdown.d.ts +11 -0
- package/dist/cli/shutdown.d.ts.map +1 -0
- package/dist/cli/shutdown.js +34 -0
- package/dist/cli/shutdown.js.map +1 -0
- package/dist/config/defaults.d.ts +6 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +48 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/effective.d.ts +26 -0
- package/dist/config/effective.d.ts.map +1 -0
- package/dist/config/effective.js +84 -0
- package/dist/config/effective.js.map +1 -0
- package/dist/config/effective.test.d.ts +2 -0
- package/dist/config/effective.test.d.ts.map +1 -0
- package/dist/config/effective.test.js +65 -0
- package/dist/config/effective.test.js.map +1 -0
- package/dist/config/loader.d.ts +12 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +212 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/loader.test.d.ts +2 -0
- package/dist/config/loader.test.d.ts.map +1 -0
- package/dist/config/loader.test.js +77 -0
- package/dist/config/loader.test.js.map +1 -0
- package/dist/config/types.d.ts +133 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +2 -0
- package/dist/config/types.js.map +1 -0
- package/dist/config/validation.d.ts +10 -0
- package/dist/config/validation.d.ts.map +1 -0
- package/dist/config/validation.js +50 -0
- package/dist/config/validation.js.map +1 -0
- package/dist/env/load-env.d.ts +6 -0
- package/dist/env/load-env.d.ts.map +1 -0
- package/dist/env/load-env.js +18 -0
- package/dist/env/load-env.js.map +1 -0
- package/dist/plugins/loader.d.ts +7 -0
- package/dist/plugins/loader.d.ts.map +1 -0
- package/dist/plugins/loader.js +70 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/plugins/manager.d.ts +11 -0
- package/dist/plugins/manager.d.ts.map +1 -0
- package/dist/plugins/manager.js +52 -0
- package/dist/plugins/manager.js.map +1 -0
- package/dist/plugins/registry.d.ts +8 -0
- package/dist/plugins/registry.d.ts.map +1 -0
- package/dist/plugins/registry.js +39 -0
- package/dist/plugins/registry.js.map +1 -0
- package/dist/proxy/buyer-proxy.d.ts +30 -0
- package/dist/proxy/buyer-proxy.d.ts.map +1 -0
- package/dist/proxy/buyer-proxy.js +488 -0
- package/dist/proxy/buyer-proxy.js.map +1 -0
- package/dist/status/node-status.d.ts +22 -0
- package/dist/status/node-status.d.ts.map +1 -0
- package/dist/status/node-status.js +83 -0
- package/dist/status/node-status.js.map +1 -0
- package/package.json +39 -0
- package/src/cli/commands/balance.ts +77 -0
- package/src/cli/commands/browse.ts +113 -0
- package/src/cli/commands/config.ts +271 -0
- package/src/cli/commands/connect.test.ts +69 -0
- package/src/cli/commands/connect.ts +342 -0
- package/src/cli/commands/dashboard.ts +59 -0
- package/src/cli/commands/deposit.ts +61 -0
- package/src/cli/commands/dev.ts +107 -0
- package/src/cli/commands/init.ts +99 -0
- package/src/cli/commands/plugin-create.test.ts +60 -0
- package/src/cli/commands/plugin-create.ts +230 -0
- package/src/cli/commands/plugin.test.ts +55 -0
- package/src/cli/commands/plugin.ts +295 -0
- package/src/cli/commands/profile.ts +95 -0
- package/src/cli/commands/seed.test.ts +70 -0
- package/src/cli/commands/seed.ts +447 -0
- package/src/cli/commands/status.ts +73 -0
- package/src/cli/commands/types.ts +56 -0
- package/src/cli/commands/withdraw.ts +61 -0
- package/src/cli/formatters.ts +64 -0
- package/src/cli/index.ts +46 -0
- package/src/cli/shutdown.ts +38 -0
- package/src/config/defaults.ts +49 -0
- package/src/config/effective.test.ts +80 -0
- package/src/config/effective.ts +119 -0
- package/src/config/loader.test.ts +95 -0
- package/src/config/loader.ts +251 -0
- package/src/config/types.ts +139 -0
- package/src/config/validation.ts +78 -0
- package/src/env/load-env.ts +20 -0
- package/src/plugins/loader.ts +96 -0
- package/src/plugins/manager.ts +66 -0
- package/src/plugins/registry.ts +45 -0
- package/src/proxy/buyer-proxy.ts +604 -0
- package/src/status/node-status.ts +105 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import type {
|
|
5
|
+
HierarchicalPricingConfig,
|
|
6
|
+
AntseedConfig,
|
|
7
|
+
ProviderPricingConfig,
|
|
8
|
+
TokenPricingUsdPerMillion,
|
|
9
|
+
} from './types.js';
|
|
10
|
+
import { createDefaultConfig } from './defaults.js';
|
|
11
|
+
import { assertValidConfig } from './validation.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolve a config path, expanding ~ to the user's home directory.
|
|
15
|
+
*/
|
|
16
|
+
function resolveConfigPath(configPath: string): string {
|
|
17
|
+
if (configPath.startsWith('~')) {
|
|
18
|
+
return resolve(homedir(), configPath.slice(2));
|
|
19
|
+
}
|
|
20
|
+
return resolve(configPath);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
24
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function toFiniteOrNaN(value: unknown): number {
|
|
28
|
+
return typeof value === 'number' ? value : Number.NaN;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function clonePricing(pricing: TokenPricingUsdPerMillion): TokenPricingUsdPerMillion {
|
|
32
|
+
return {
|
|
33
|
+
inputUsdPerMillion: pricing.inputUsdPerMillion,
|
|
34
|
+
outputUsdPerMillion: pricing.outputUsdPerMillion,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function normalizeTokenPricing(value: unknown): TokenPricingUsdPerMillion | null {
|
|
39
|
+
if (!isRecord(value)) return null;
|
|
40
|
+
return {
|
|
41
|
+
inputUsdPerMillion: toFiniteOrNaN(value['inputUsdPerMillion']),
|
|
42
|
+
outputUsdPerMillion: toFiniteOrNaN(value['outputUsdPerMillion']),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function mergeTokenPricing(
|
|
47
|
+
defaults: TokenPricingUsdPerMillion,
|
|
48
|
+
value: unknown
|
|
49
|
+
): TokenPricingUsdPerMillion {
|
|
50
|
+
if (!isRecord(value)) {
|
|
51
|
+
return clonePricing(defaults);
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
inputUsdPerMillion: typeof value['inputUsdPerMillion'] === 'number'
|
|
55
|
+
? value['inputUsdPerMillion']
|
|
56
|
+
: defaults.inputUsdPerMillion,
|
|
57
|
+
outputUsdPerMillion: typeof value['outputUsdPerMillion'] === 'number'
|
|
58
|
+
? value['outputUsdPerMillion']
|
|
59
|
+
: defaults.outputUsdPerMillion,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function cloneProviderPricing(
|
|
64
|
+
providers: Record<string, ProviderPricingConfig> | undefined
|
|
65
|
+
): Record<string, ProviderPricingConfig> | undefined {
|
|
66
|
+
if (!providers) return undefined;
|
|
67
|
+
const out: Record<string, ProviderPricingConfig> = {};
|
|
68
|
+
for (const [provider, cfg] of Object.entries(providers)) {
|
|
69
|
+
out[provider] = {
|
|
70
|
+
...(cfg.defaults ? { defaults: clonePricing(cfg.defaults) } : {}),
|
|
71
|
+
...(cfg.models ? { models: { ...cfg.models } } : {}),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function mergeModelPricing(
|
|
78
|
+
defaults: Record<string, TokenPricingUsdPerMillion> | undefined,
|
|
79
|
+
value: unknown
|
|
80
|
+
): Record<string, TokenPricingUsdPerMillion> | undefined {
|
|
81
|
+
const out: Record<string, TokenPricingUsdPerMillion> = {
|
|
82
|
+
...(defaults ?? {}),
|
|
83
|
+
};
|
|
84
|
+
if (isRecord(value)) {
|
|
85
|
+
for (const [model, rawPricing] of Object.entries(value)) {
|
|
86
|
+
const parsed = normalizeTokenPricing(rawPricing);
|
|
87
|
+
if (parsed) {
|
|
88
|
+
out[model] = parsed;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function mergeProviderPricing(
|
|
96
|
+
defaults: Record<string, ProviderPricingConfig> | undefined,
|
|
97
|
+
value: unknown
|
|
98
|
+
): Record<string, ProviderPricingConfig> | undefined {
|
|
99
|
+
const out = cloneProviderPricing(defaults) ?? {};
|
|
100
|
+
if (isRecord(value)) {
|
|
101
|
+
for (const [provider, rawCfg] of Object.entries(value)) {
|
|
102
|
+
if (!isRecord(rawCfg)) continue;
|
|
103
|
+
const existing = out[provider];
|
|
104
|
+
const parsedDefaults = normalizeTokenPricing(rawCfg['defaults']);
|
|
105
|
+
const next: ProviderPricingConfig = {
|
|
106
|
+
...(parsedDefaults ? { defaults: parsedDefaults } : (existing?.defaults ? { defaults: existing.defaults } : {})),
|
|
107
|
+
};
|
|
108
|
+
const mergedModels = mergeModelPricing(existing?.models, rawCfg['models']);
|
|
109
|
+
if (mergedModels) {
|
|
110
|
+
next.models = mergedModels;
|
|
111
|
+
}
|
|
112
|
+
if (next.defaults || next.models) {
|
|
113
|
+
out[provider] = next;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function mergeHierarchicalPricing(
|
|
121
|
+
defaults: HierarchicalPricingConfig,
|
|
122
|
+
value: unknown
|
|
123
|
+
): HierarchicalPricingConfig {
|
|
124
|
+
if (!isRecord(value)) {
|
|
125
|
+
return {
|
|
126
|
+
defaults: clonePricing(defaults.defaults),
|
|
127
|
+
...(defaults.providers ? { providers: cloneProviderPricing(defaults.providers) } : {}),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const mergedProviders = mergeProviderPricing(defaults.providers, value['providers']);
|
|
131
|
+
return {
|
|
132
|
+
defaults: mergeTokenPricing(defaults.defaults, value['defaults']),
|
|
133
|
+
...(mergedProviders ? { providers: mergedProviders } : {}),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function mergeSellerConfig(
|
|
138
|
+
defaults: AntseedConfig['seller'],
|
|
139
|
+
value: unknown
|
|
140
|
+
): AntseedConfig['seller'] {
|
|
141
|
+
if (!isRecord(value)) {
|
|
142
|
+
return {
|
|
143
|
+
reserveFloor: defaults.reserveFloor,
|
|
144
|
+
maxConcurrentBuyers: defaults.maxConcurrentBuyers,
|
|
145
|
+
enabledProviders: [...defaults.enabledProviders],
|
|
146
|
+
pricing: mergeHierarchicalPricing(defaults.pricing, undefined),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
reserveFloor: typeof value['reserveFloor'] === 'number'
|
|
151
|
+
? value['reserveFloor']
|
|
152
|
+
: defaults.reserveFloor,
|
|
153
|
+
maxConcurrentBuyers: typeof value['maxConcurrentBuyers'] === 'number'
|
|
154
|
+
? value['maxConcurrentBuyers']
|
|
155
|
+
: defaults.maxConcurrentBuyers,
|
|
156
|
+
enabledProviders: Array.isArray(value['enabledProviders'])
|
|
157
|
+
? value['enabledProviders'].filter((entry): entry is string => typeof entry === 'string')
|
|
158
|
+
: [...defaults.enabledProviders],
|
|
159
|
+
pricing: mergeHierarchicalPricing(defaults.pricing, value['pricing']),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function mergeBuyerConfig(
|
|
164
|
+
defaults: AntseedConfig['buyer'],
|
|
165
|
+
value: unknown
|
|
166
|
+
): AntseedConfig['buyer'] {
|
|
167
|
+
if (!isRecord(value)) {
|
|
168
|
+
return {
|
|
169
|
+
preferredProviders: [...defaults.preferredProviders],
|
|
170
|
+
maxPricing: mergeHierarchicalPricing(defaults.maxPricing, undefined),
|
|
171
|
+
minPeerReputation: defaults.minPeerReputation,
|
|
172
|
+
proxyPort: defaults.proxyPort,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
preferredProviders: Array.isArray(value['preferredProviders'])
|
|
177
|
+
? value['preferredProviders'].filter((entry): entry is string => typeof entry === 'string')
|
|
178
|
+
: [...defaults.preferredProviders],
|
|
179
|
+
maxPricing: mergeHierarchicalPricing(defaults.maxPricing, value['maxPricing']),
|
|
180
|
+
minPeerReputation: typeof value['minPeerReputation'] === 'number'
|
|
181
|
+
? value['minPeerReputation']
|
|
182
|
+
: defaults.minPeerReputation,
|
|
183
|
+
proxyPort: typeof value['proxyPort'] === 'number'
|
|
184
|
+
? value['proxyPort']
|
|
185
|
+
: defaults.proxyPort,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Load configuration from a JSON file.
|
|
191
|
+
* Returns default configuration if the file does not exist.
|
|
192
|
+
*/
|
|
193
|
+
export async function loadConfig(configPath: string): Promise<AntseedConfig> {
|
|
194
|
+
const resolved = resolveConfigPath(configPath);
|
|
195
|
+
|
|
196
|
+
let raw: string;
|
|
197
|
+
try {
|
|
198
|
+
raw = await readFile(resolved, 'utf-8');
|
|
199
|
+
} catch (err: unknown) {
|
|
200
|
+
if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
201
|
+
return createDefaultConfig();
|
|
202
|
+
}
|
|
203
|
+
throw err;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
let parsedRaw: unknown;
|
|
207
|
+
try {
|
|
208
|
+
parsedRaw = JSON.parse(raw);
|
|
209
|
+
} catch {
|
|
210
|
+
console.warn(`Warning: Could not parse config at ${resolved}. Using defaults.`);
|
|
211
|
+
return createDefaultConfig();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const defaults = createDefaultConfig();
|
|
215
|
+
const parsed = isRecord(parsedRaw) ? parsedRaw : {};
|
|
216
|
+
|
|
217
|
+
const merged: AntseedConfig = {
|
|
218
|
+
...defaults,
|
|
219
|
+
...(parsed as Partial<AntseedConfig>),
|
|
220
|
+
identity: {
|
|
221
|
+
...defaults.identity,
|
|
222
|
+
...(isRecord(parsed['identity']) ? parsed['identity'] : {}),
|
|
223
|
+
},
|
|
224
|
+
seller: mergeSellerConfig(defaults.seller, parsed['seller']),
|
|
225
|
+
buyer: mergeBuyerConfig(defaults.buyer, parsed['buyer']),
|
|
226
|
+
payments: {
|
|
227
|
+
...defaults.payments,
|
|
228
|
+
...(isRecord(parsed['payments']) ? parsed['payments'] : {}),
|
|
229
|
+
},
|
|
230
|
+
network: {
|
|
231
|
+
...defaults.network,
|
|
232
|
+
...(isRecord(parsed['network']) ? parsed['network'] : {}),
|
|
233
|
+
},
|
|
234
|
+
providers: Array.isArray(parsed['providers'])
|
|
235
|
+
? (parsed['providers'] as AntseedConfig['providers'])
|
|
236
|
+
: defaults.providers,
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
assertValidConfig(merged);
|
|
240
|
+
return merged;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Save configuration to a JSON file.
|
|
245
|
+
* Creates the directory if it doesn't exist.
|
|
246
|
+
*/
|
|
247
|
+
export async function saveConfig(configPath: string, config: AntseedConfig): Promise<void> {
|
|
248
|
+
const resolved = resolveConfigPath(configPath);
|
|
249
|
+
await mkdir(dirname(resolved), { recursive: true });
|
|
250
|
+
await writeFile(resolved, JSON.stringify(config, null, 2), 'utf-8');
|
|
251
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { ProviderType } from '@antseed/node';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Provider configuration for the Antseed config file.
|
|
5
|
+
* This is distinct from the provider.ts ProviderConfig used internally.
|
|
6
|
+
*/
|
|
7
|
+
export interface CLIProviderConfig {
|
|
8
|
+
/** Provider type identifier */
|
|
9
|
+
type: ProviderType;
|
|
10
|
+
/** API endpoint URL */
|
|
11
|
+
endpoint: string;
|
|
12
|
+
/** Name of the HTTP header used for authentication */
|
|
13
|
+
authHeaderName: string;
|
|
14
|
+
/** Auth token / API key value */
|
|
15
|
+
authValue: string;
|
|
16
|
+
/** Auth type: 'apikey' (default), 'oauth' (with refresh), or 'claude-code' (read from keychain) */
|
|
17
|
+
authType?: 'apikey' | 'oauth' | 'claude-code';
|
|
18
|
+
/** OAuth refresh token (required when authType is 'oauth') */
|
|
19
|
+
refreshToken?: string;
|
|
20
|
+
/** Token expiration timestamp in epoch ms (used with authType 'oauth') */
|
|
21
|
+
expiresAt?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Re-export ProviderType for convenience in config commands.
|
|
26
|
+
*/
|
|
27
|
+
export type { ProviderType } from '@antseed/node';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Dual token pricing in USD per 1M tokens.
|
|
31
|
+
*/
|
|
32
|
+
export interface TokenPricingUsdPerMillion {
|
|
33
|
+
inputUsdPerMillion: number;
|
|
34
|
+
outputUsdPerMillion: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Provider-level optional defaults and per-model overrides.
|
|
39
|
+
*/
|
|
40
|
+
export interface ProviderPricingConfig {
|
|
41
|
+
defaults?: TokenPricingUsdPerMillion;
|
|
42
|
+
models?: Record<string, TokenPricingUsdPerMillion>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Hierarchical pricing with global defaults and optional provider/model overrides.
|
|
47
|
+
*/
|
|
48
|
+
export interface HierarchicalPricingConfig {
|
|
49
|
+
defaults: TokenPricingUsdPerMillion;
|
|
50
|
+
providers?: Record<string, ProviderPricingConfig>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Seller-specific configuration within the Antseed config.
|
|
55
|
+
*/
|
|
56
|
+
export interface SellerCLIConfig {
|
|
57
|
+
/** Reserve floor in messages per hour to keep for yourself */
|
|
58
|
+
reserveFloor: number;
|
|
59
|
+
/** Maximum number of concurrent buyer connections */
|
|
60
|
+
maxConcurrentBuyers: number;
|
|
61
|
+
/** Which provider types are enabled for selling */
|
|
62
|
+
enabledProviders: string[];
|
|
63
|
+
/** Seller offer pricing rules in USD per 1M tokens */
|
|
64
|
+
pricing: HierarchicalPricingConfig;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Buyer-specific configuration within the Antseed config.
|
|
69
|
+
*/
|
|
70
|
+
export interface BuyerCLIConfig {
|
|
71
|
+
/** Preferred provider types for purchasing */
|
|
72
|
+
preferredProviders: string[];
|
|
73
|
+
/** Buyer max willing-to-pay rules in USD per 1M tokens */
|
|
74
|
+
maxPricing: HierarchicalPricingConfig;
|
|
75
|
+
/** Minimum peer reputation score (0-100) */
|
|
76
|
+
minPeerReputation: number;
|
|
77
|
+
/** Local proxy listen port */
|
|
78
|
+
proxyPort: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Payment configuration within the Antseed config.
|
|
83
|
+
*/
|
|
84
|
+
export interface PaymentsCLIConfig {
|
|
85
|
+
/** Preferred payment method */
|
|
86
|
+
preferredMethod: 'crypto';
|
|
87
|
+
/** Platform fee rate (0-1) */
|
|
88
|
+
platformFeeRate: number;
|
|
89
|
+
/** Optional crypto settlement settings (Base network) */
|
|
90
|
+
crypto?: {
|
|
91
|
+
/** Chain identifier */
|
|
92
|
+
chainId: 'base-local' | 'base-sepolia' | 'base-mainnet';
|
|
93
|
+
/** Base JSON-RPC URL (e.g. http://127.0.0.1:8545 for local anvil) */
|
|
94
|
+
rpcUrl: string;
|
|
95
|
+
/** Deployed AntseedEscrow contract address */
|
|
96
|
+
escrowContractAddress: string;
|
|
97
|
+
/** USDC token contract address */
|
|
98
|
+
usdcContractAddress: string;
|
|
99
|
+
/** Default lock amount per session in human-readable USDC (e.g. "1" = 1 USDC) */
|
|
100
|
+
defaultLockAmountUSDC?: string;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Network configuration within the Antseed config.
|
|
106
|
+
*/
|
|
107
|
+
export interface NetworkCLIConfig {
|
|
108
|
+
/** Additional bootstrap nodes for DHT discovery (host:port pairs) */
|
|
109
|
+
bootstrapNodes: string[];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Top-level Antseed configuration structure.
|
|
114
|
+
*/
|
|
115
|
+
export interface AntseedConfig {
|
|
116
|
+
/** Node identity information (peer ID, display name) */
|
|
117
|
+
identity: {
|
|
118
|
+
displayName: string;
|
|
119
|
+
walletAddress?: string;
|
|
120
|
+
};
|
|
121
|
+
/** Configured LLM provider credentials */
|
|
122
|
+
providers: CLIProviderConfig[];
|
|
123
|
+
/** Seller mode settings */
|
|
124
|
+
seller: SellerCLIConfig;
|
|
125
|
+
/** Buyer mode settings */
|
|
126
|
+
buyer: BuyerCLIConfig;
|
|
127
|
+
/** Payment settings */
|
|
128
|
+
payments: PaymentsCLIConfig;
|
|
129
|
+
/** Network / DHT settings */
|
|
130
|
+
network: NetworkCLIConfig;
|
|
131
|
+
/** Installed plugins */
|
|
132
|
+
plugins?: { name: string; package: string; installedAt: string }[];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* ProviderConfig alias for use in config commands.
|
|
137
|
+
* Maps to CLIProviderConfig.
|
|
138
|
+
*/
|
|
139
|
+
export type ProviderConfig = CLIProviderConfig;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
HierarchicalPricingConfig,
|
|
3
|
+
AntseedConfig,
|
|
4
|
+
TokenPricingUsdPerMillion,
|
|
5
|
+
} from './types.js';
|
|
6
|
+
|
|
7
|
+
function validatePricingLeaf(
|
|
8
|
+
path: string,
|
|
9
|
+
value: TokenPricingUsdPerMillion,
|
|
10
|
+
errors: string[]
|
|
11
|
+
): void {
|
|
12
|
+
if (!Number.isFinite(value.inputUsdPerMillion) || value.inputUsdPerMillion < 0) {
|
|
13
|
+
errors.push(`${path}.inputUsdPerMillion must be a non-negative finite number`);
|
|
14
|
+
}
|
|
15
|
+
if (!Number.isFinite(value.outputUsdPerMillion) || value.outputUsdPerMillion < 0) {
|
|
16
|
+
errors.push(`${path}.outputUsdPerMillion must be a non-negative finite number`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function validateHierarchicalPricing(
|
|
21
|
+
path: string,
|
|
22
|
+
pricing: HierarchicalPricingConfig,
|
|
23
|
+
errors: string[]
|
|
24
|
+
): void {
|
|
25
|
+
validatePricingLeaf(`${path}.defaults`, pricing.defaults, errors);
|
|
26
|
+
|
|
27
|
+
for (const [provider, providerPricing] of Object.entries(pricing.providers ?? {})) {
|
|
28
|
+
if (providerPricing.defaults) {
|
|
29
|
+
validatePricingLeaf(`${path}.providers.${provider}.defaults`, providerPricing.defaults, errors);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for (const [model, modelPricing] of Object.entries(providerPricing.models ?? {})) {
|
|
33
|
+
validatePricingLeaf(
|
|
34
|
+
`${path}.providers.${provider}.models.${model}`,
|
|
35
|
+
modelPricing,
|
|
36
|
+
errors
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Validate the full config and return all issues.
|
|
44
|
+
*/
|
|
45
|
+
export function validateConfig(config: AntseedConfig): string[] {
|
|
46
|
+
const errors: string[] = [];
|
|
47
|
+
|
|
48
|
+
validateHierarchicalPricing('seller.pricing', config.seller.pricing, errors);
|
|
49
|
+
validateHierarchicalPricing('buyer.maxPricing', config.buyer.maxPricing, errors);
|
|
50
|
+
|
|
51
|
+
if (!Number.isFinite(config.buyer.minPeerReputation) || config.buyer.minPeerReputation < 0 || config.buyer.minPeerReputation > 100) {
|
|
52
|
+
errors.push('buyer.minPeerReputation must be in range 0-100');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!Number.isInteger(config.buyer.proxyPort) || config.buyer.proxyPort < 1 || config.buyer.proxyPort > 65535) {
|
|
56
|
+
errors.push('buyer.proxyPort must be an integer in range 1-65535');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!Number.isInteger(config.seller.maxConcurrentBuyers) || config.seller.maxConcurrentBuyers < 1) {
|
|
60
|
+
errors.push('seller.maxConcurrentBuyers must be an integer >= 1');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!Number.isFinite(config.seller.reserveFloor) || config.seller.reserveFloor < 0) {
|
|
64
|
+
errors.push('seller.reserveFloor must be a non-negative finite number');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return errors;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Assert that config is valid. Throws with all discovered violations.
|
|
72
|
+
*/
|
|
73
|
+
export function assertValidConfig(config: AntseedConfig): void {
|
|
74
|
+
const errors = validateConfig(config);
|
|
75
|
+
if (errors.length === 0) return;
|
|
76
|
+
|
|
77
|
+
throw new Error(`Invalid config:\n- ${errors.join('\n- ')}`);
|
|
78
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import dotenv from 'dotenv';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
function applyEnvFile(filePath: string): void {
|
|
5
|
+
dotenv.config({ path: filePath, override: false });
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Load runtime environment variables from .env files.
|
|
10
|
+
* Precedence: existing process.env > explicit env file > .env.local > .env
|
|
11
|
+
*/
|
|
12
|
+
export function loadEnvFromFiles(): void {
|
|
13
|
+
const explicit = process.env['ANTSEED_ENV_FILE'];
|
|
14
|
+
if (explicit) {
|
|
15
|
+
applyEnvFile(resolve(explicit));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
applyEnvFile(resolve(process.cwd(), '.env.local'));
|
|
19
|
+
applyEnvFile(resolve(process.cwd(), '.env'));
|
|
20
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import path, { join } from 'node:path'
|
|
2
|
+
import { getPluginsDir } from './manager.js'
|
|
3
|
+
import { TRUSTED_PLUGINS } from './registry.js'
|
|
4
|
+
import type { AntseedProviderPlugin, AntseedRouterPlugin, PluginConfigKey } from '@antseed/node'
|
|
5
|
+
|
|
6
|
+
function resolvePackageName(nameOrPackage: string): string {
|
|
7
|
+
const legacy = LEGACY_PACKAGE_MAP[nameOrPackage]
|
|
8
|
+
if (legacy) return legacy
|
|
9
|
+
const trusted = TRUSTED_PLUGINS.find(p => p.name === nameOrPackage)
|
|
10
|
+
return trusted?.package ?? nameOrPackage
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type PluginKind = 'provider' | 'router'
|
|
14
|
+
|
|
15
|
+
async function loadPlugin<T>(
|
|
16
|
+
nameOrPackage: string,
|
|
17
|
+
kind: PluginKind,
|
|
18
|
+
methodName: keyof AntseedProviderPlugin | keyof AntseedRouterPlugin
|
|
19
|
+
): Promise<T> {
|
|
20
|
+
const pkgName = resolvePackageName(nameOrPackage)
|
|
21
|
+
const pluginsDir = getPluginsDir()
|
|
22
|
+
const pluginPath = join(pluginsDir, 'node_modules', pkgName, 'dist', 'index.js')
|
|
23
|
+
const resolved = path.resolve(pluginPath)
|
|
24
|
+
if (!resolved.startsWith(path.resolve(pluginsDir))) {
|
|
25
|
+
throw new Error(`Invalid plugin path: ${pkgName}`)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let mod: { default?: unknown }
|
|
29
|
+
try {
|
|
30
|
+
mod = await import(resolved) as { default?: unknown }
|
|
31
|
+
} catch (err) {
|
|
32
|
+
const cause = err instanceof Error ? err.message : String(err)
|
|
33
|
+
if (
|
|
34
|
+
err &&
|
|
35
|
+
typeof err === 'object' &&
|
|
36
|
+
'code' in err &&
|
|
37
|
+
(err as { code?: string }).code === 'ERR_MODULE_NOT_FOUND'
|
|
38
|
+
) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Plugin "${pkgName}" not found. Install it with:\n antseed plugin add ${pkgName}`
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Plugin "${pkgName}" failed to load from ${resolved}.\nCause: ${cause}`
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const plugin = mod.default
|
|
49
|
+
if (!plugin || typeof plugin !== 'object' || (plugin as { type?: string }).type !== kind) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`Plugin "${pkgName}" does not export a valid ${kind} plugin (expected default export with type: '${kind}')`
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (typeof (plugin as Record<string, unknown>)[methodName] !== 'function') {
|
|
56
|
+
throw new Error(`Plugin "${pkgName}" does not implement ${methodName}()`)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return plugin as T
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function loadProviderPlugin(nameOrPackage: string): Promise<AntseedProviderPlugin> {
|
|
63
|
+
return loadPlugin<AntseedProviderPlugin>(nameOrPackage, 'provider', 'createProvider')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function loadRouterPlugin(nameOrPackage: string): Promise<AntseedRouterPlugin> {
|
|
67
|
+
return loadPlugin<AntseedRouterPlugin>(nameOrPackage, 'router', 'createRouter')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function buildPluginConfig(
|
|
71
|
+
configKeys: PluginConfigKey[],
|
|
72
|
+
runtimeOverrides?: Record<string, string>,
|
|
73
|
+
instanceConfig?: Record<string, string>,
|
|
74
|
+
): Record<string, string> {
|
|
75
|
+
const config: Record<string, string> = {}
|
|
76
|
+
// Priority: instanceConfig (lowest) < env vars < runtime overrides (highest)
|
|
77
|
+
if (instanceConfig) {
|
|
78
|
+
Object.assign(config, instanceConfig)
|
|
79
|
+
}
|
|
80
|
+
for (const key of configKeys) {
|
|
81
|
+
const value = process.env[key.key]
|
|
82
|
+
if (value !== undefined) {
|
|
83
|
+
config[key.key] = value
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (runtimeOverrides) {
|
|
87
|
+
Object.assign(config, runtimeOverrides)
|
|
88
|
+
}
|
|
89
|
+
return config
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Map legacy package names to current names */
|
|
93
|
+
export const LEGACY_PACKAGE_MAP: Record<string, string> = {
|
|
94
|
+
'antseed-provider-anthropic': '@antseed/provider-claude-code',
|
|
95
|
+
'antseed-router-claude-code': '@antseed/router-local-proxy',
|
|
96
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process'
|
|
2
|
+
import { promisify } from 'node:util'
|
|
3
|
+
import { mkdir, readFile, writeFile, access } from 'node:fs/promises'
|
|
4
|
+
import { constants, existsSync } from 'node:fs'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
import { homedir } from 'node:os'
|
|
7
|
+
|
|
8
|
+
const execFileAsync = promisify(execFile)
|
|
9
|
+
|
|
10
|
+
export const PLUGINS_DIR = join(homedir(), '.antseed', 'plugins')
|
|
11
|
+
|
|
12
|
+
function resolvePluginsDir(): string {
|
|
13
|
+
if (existsSync(PLUGINS_DIR)) return PLUGINS_DIR
|
|
14
|
+
return PLUGINS_DIR
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getPluginsDir(): string {
|
|
18
|
+
return resolvePluginsDir()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface InstalledPlugin {
|
|
22
|
+
name: string
|
|
23
|
+
package: string
|
|
24
|
+
version: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function ensurePluginsDir(): Promise<void> {
|
|
28
|
+
const pluginsDir = resolvePluginsDir()
|
|
29
|
+
const pluginsPackageJson = join(pluginsDir, 'package.json')
|
|
30
|
+
await mkdir(pluginsDir, { recursive: true })
|
|
31
|
+
try {
|
|
32
|
+
await access(pluginsPackageJson, constants.R_OK)
|
|
33
|
+
} catch {
|
|
34
|
+
await writeFile(
|
|
35
|
+
pluginsPackageJson,
|
|
36
|
+
JSON.stringify({ name: 'antseed-plugins', version: '1.0.0', private: true, dependencies: {} }, null, 2),
|
|
37
|
+
'utf-8'
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function installPlugin(packageName: string): Promise<void> {
|
|
43
|
+
await ensurePluginsDir()
|
|
44
|
+
await execFileAsync('npm', ['install', '--ignore-scripts', packageName], { cwd: getPluginsDir() })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function removePlugin(packageName: string): Promise<void> {
|
|
48
|
+
await ensurePluginsDir()
|
|
49
|
+
await execFileAsync('npm', ['uninstall', packageName], { cwd: getPluginsDir() })
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function listInstalledPlugins(): Promise<InstalledPlugin[]> {
|
|
53
|
+
await ensurePluginsDir()
|
|
54
|
+
try {
|
|
55
|
+
const raw = await readFile(join(getPluginsDir(), 'package.json'), 'utf-8')
|
|
56
|
+
const pkg = JSON.parse(raw) as { dependencies?: Record<string, string> }
|
|
57
|
+
const deps = pkg.dependencies ?? {}
|
|
58
|
+
return Object.entries(deps).map(([pkgName, version]) => ({
|
|
59
|
+
name: pkgName,
|
|
60
|
+
package: pkgName,
|
|
61
|
+
version: version,
|
|
62
|
+
}))
|
|
63
|
+
} catch {
|
|
64
|
+
return []
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface TrustedPlugin {
|
|
2
|
+
name: string
|
|
3
|
+
type: 'provider' | 'router'
|
|
4
|
+
description: string
|
|
5
|
+
package: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const TRUSTED_PLUGINS: TrustedPlugin[] = [
|
|
9
|
+
{
|
|
10
|
+
name: 'anthropic',
|
|
11
|
+
type: 'provider',
|
|
12
|
+
description: 'Sell Anthropic API capacity (API key)',
|
|
13
|
+
package: '@antseed/provider-anthropic',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'claude-code',
|
|
17
|
+
type: 'provider',
|
|
18
|
+
description: 'Sell Claude capacity via Claude Code keychain',
|
|
19
|
+
package: '@antseed/provider-claude-code',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'openrouter',
|
|
23
|
+
type: 'provider',
|
|
24
|
+
description: 'Sell via OpenRouter (multi-model)',
|
|
25
|
+
package: '@antseed/provider-openrouter',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'local-llm',
|
|
29
|
+
type: 'provider',
|
|
30
|
+
description: 'Sell local LLM capacity (Ollama, llama.cpp)',
|
|
31
|
+
package: '@antseed/provider-local-llm',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'local-proxy',
|
|
35
|
+
type: 'router',
|
|
36
|
+
description: 'Local HTTP proxy for Claude Code, Aider, Codex',
|
|
37
|
+
package: '@antseed/router-local-proxy',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'local-chat',
|
|
41
|
+
type: 'router',
|
|
42
|
+
description: 'Local desktop chat router',
|
|
43
|
+
package: '@antseed/router-local-chat',
|
|
44
|
+
},
|
|
45
|
+
]
|