@terminusagents/agents 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli/init.js +172 -3
- package/dist/cli/run.js +7 -3
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -53,6 +53,8 @@ npx terminus-agent init
|
|
|
53
53
|
`init` uses production-safe defaults:
|
|
54
54
|
- testnet: `wss://cp-sepolia.termn.xyz/ws`
|
|
55
55
|
- mainnet: `wss://cp-mainnet.termn.xyz/ws`
|
|
56
|
+
- if `TERMINUS_WALLET_PRIVATE_KEY` is set, wallet is derived automatically
|
|
57
|
+
- if your wallet already has a Terminus identity NFT, agent type is auto-detected from chain
|
|
56
58
|
|
|
57
59
|
3. Export required runtime secrets:
|
|
58
60
|
|
|
@@ -89,8 +91,6 @@ npx terminus-agent init \
|
|
|
89
91
|
--yes \
|
|
90
92
|
--force \
|
|
91
93
|
--profile testnet \
|
|
92
|
-
--agent-type travel-planner \
|
|
93
|
-
--wallet 0x1234567890abcdef1234567890abcdef12345678 \
|
|
94
94
|
--llm-provider openai \
|
|
95
95
|
--llm-model gpt-4o-mini
|
|
96
96
|
```
|
package/dist/cli/init.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// =============================================================================
|
|
4
4
|
import inquirer from 'inquirer';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
|
+
import { ethers } from 'ethers';
|
|
6
7
|
import { saveConfig, generateNodeId, getConfigPath, configExists, loadConfig } from '../config/store.js';
|
|
7
8
|
import { checkOllamaAvailable, listOllamaModels } from '../llm/provider.js';
|
|
8
9
|
import { AGENTS } from '../agents/index.js';
|
|
@@ -44,6 +45,15 @@ const NETWORK_LABELS = {
|
|
|
44
45
|
};
|
|
45
46
|
const DEFAULT_TESTNET_CONTROL_PLANE_URL = 'wss://cp-sepolia.termn.xyz/ws';
|
|
46
47
|
const DEFAULT_MAINNET_CONTROL_PLANE_URL = 'wss://cp-mainnet.termn.xyz/ws';
|
|
48
|
+
const DEFAULT_TESTNET_REGISTRY_CONTRACT = '0x1439F2c1Fb1B3F99efe2aB02A90B377BaE5D2B02';
|
|
49
|
+
const DEFAULT_TESTNET_RPC_URL = 'https://sepolia.base.org';
|
|
50
|
+
const DEFAULT_MAINNET_RPC_URL = 'https://mainnet.base.org';
|
|
51
|
+
const AGENT_IDENTITY_REGISTRY_ABI = [
|
|
52
|
+
'function balanceOf(address owner) view returns (uint256)',
|
|
53
|
+
'function tokenOfOwnerByIndex(address owner, uint256 index) view returns (uint256)',
|
|
54
|
+
'function getAgentType(uint256 tokenId) view returns (string)',
|
|
55
|
+
'function getCollectionTokenId(uint256 tokenId) view returns (uint256)',
|
|
56
|
+
];
|
|
47
57
|
const NETWORK_URL_PRESETS = {
|
|
48
58
|
local: process.env.TERMINUS_CONTROL_PLANE_URL_LOCAL?.trim() || 'ws://localhost:8084/ws',
|
|
49
59
|
testnet: process.env.TERMINUS_CONTROL_PLANE_URL_TESTNET?.trim() ||
|
|
@@ -183,6 +193,145 @@ function providerDefaultModel(provider) {
|
|
|
183
193
|
return 'claude-3-5-haiku-latest';
|
|
184
194
|
return 'gemini-2.0-flash';
|
|
185
195
|
}
|
|
196
|
+
function getWalletFromRuntimePrivateKey() {
|
|
197
|
+
const privateKey = process.env.TERMINUS_WALLET_PRIVATE_KEY?.trim();
|
|
198
|
+
if (!privateKey)
|
|
199
|
+
return undefined;
|
|
200
|
+
try {
|
|
201
|
+
const normalizedPk = privateKey.startsWith('0x') ? privateKey : `0x${privateKey}`;
|
|
202
|
+
return new ethers.Wallet(normalizedPk).address;
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function resolveRegistryAddress(profile) {
|
|
209
|
+
if (profile === 'testnet') {
|
|
210
|
+
const candidate = (process.env.TERMINUS_AGENT_IDENTITY_REGISTRY_CONTRACT_TESTNET?.trim()
|
|
211
|
+
|| process.env.AGENT_IDENTITY_REGISTRY_CONTRACT_TESTNET?.trim()
|
|
212
|
+
|| process.env.NEXT_PUBLIC_SEPOLIA_REGISTRY_CONTRACT?.trim()
|
|
213
|
+
|| process.env.AGENT_IDENTITY_REGISTRY_CONTRACT?.trim()
|
|
214
|
+
|| DEFAULT_TESTNET_REGISTRY_CONTRACT);
|
|
215
|
+
return isValidWallet(candidate) ? candidate : undefined;
|
|
216
|
+
}
|
|
217
|
+
if (profile === 'mainnet') {
|
|
218
|
+
const candidate = (process.env.TERMINUS_AGENT_IDENTITY_REGISTRY_CONTRACT_MAINNET?.trim()
|
|
219
|
+
|| process.env.AGENT_IDENTITY_REGISTRY_CONTRACT_MAINNET?.trim()
|
|
220
|
+
|| process.env.NEXT_PUBLIC_MAINNET_REGISTRY_CONTRACT?.trim()
|
|
221
|
+
|| process.env.AGENT_IDENTITY_REGISTRY_CONTRACT?.trim());
|
|
222
|
+
return candidate && isValidWallet(candidate) ? candidate : undefined;
|
|
223
|
+
}
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
function resolveRpcUrl(profile) {
|
|
227
|
+
if (profile === 'testnet') {
|
|
228
|
+
return (process.env.TERMINUS_BASE_SEPOLIA_RPC?.trim()
|
|
229
|
+
|| process.env.BASE_SEPOLIA_RPC?.trim()
|
|
230
|
+
|| DEFAULT_TESTNET_RPC_URL);
|
|
231
|
+
}
|
|
232
|
+
if (profile === 'mainnet') {
|
|
233
|
+
return (process.env.TERMINUS_BASE_MAINNET_RPC?.trim()
|
|
234
|
+
|| process.env.BASE_MAINNET_RPC?.trim()
|
|
235
|
+
|| DEFAULT_MAINNET_RPC_URL);
|
|
236
|
+
}
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
function resolveRequiredCollectionTokenId(profile) {
|
|
240
|
+
if (profile === 'testnet') {
|
|
241
|
+
return (process.env.TERMINUS_COLLECTION_TOKEN_ID_TESTNET?.trim()
|
|
242
|
+
|| process.env.NEXT_PUBLIC_SEPOLIA_COLLECTION_TOKEN_ID?.trim()
|
|
243
|
+
|| process.env.TERMINUS_COLLECTION_TOKEN_ID?.trim()
|
|
244
|
+
|| undefined);
|
|
245
|
+
}
|
|
246
|
+
if (profile === 'mainnet') {
|
|
247
|
+
return (process.env.TERMINUS_COLLECTION_TOKEN_ID_MAINNET?.trim()
|
|
248
|
+
|| process.env.NEXT_PUBLIC_MAINNET_COLLECTION_TOKEN_ID?.trim()
|
|
249
|
+
|| process.env.TERMINUS_COLLECTION_TOKEN_ID?.trim()
|
|
250
|
+
|| undefined);
|
|
251
|
+
}
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
async function detectAgentIdentitiesFromChain(profile, wallet) {
|
|
255
|
+
const registryAddress = resolveRegistryAddress(profile);
|
|
256
|
+
const rpcUrl = resolveRpcUrl(profile);
|
|
257
|
+
if (!registryAddress || !rpcUrl || profile === 'local') {
|
|
258
|
+
return { identities: [], registryAddress, rpcUrl };
|
|
259
|
+
}
|
|
260
|
+
const requiredCollectionTokenId = resolveRequiredCollectionTokenId(profile);
|
|
261
|
+
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
262
|
+
const contract = new ethers.Contract(registryAddress, AGENT_IDENTITY_REGISTRY_ABI, provider);
|
|
263
|
+
const balance = await contract.balanceOf(wallet);
|
|
264
|
+
if (balance === 0n) {
|
|
265
|
+
return { identities: [], registryAddress, rpcUrl };
|
|
266
|
+
}
|
|
267
|
+
const identities = [];
|
|
268
|
+
for (let i = 0n; i < balance; i++) {
|
|
269
|
+
const tokenId = await contract.tokenOfOwnerByIndex(wallet, i);
|
|
270
|
+
const agentTypeRaw = await contract.getAgentType(tokenId);
|
|
271
|
+
const agentType = agentTypeRaw.trim();
|
|
272
|
+
if (!AGENTS.some((agent) => agent.id === agentType)) {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
let collectionTokenId;
|
|
276
|
+
try {
|
|
277
|
+
const rawCollectionTokenId = await contract.getCollectionTokenId(tokenId);
|
|
278
|
+
collectionTokenId = rawCollectionTokenId.toString();
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
collectionTokenId = undefined;
|
|
282
|
+
}
|
|
283
|
+
if (requiredCollectionTokenId && collectionTokenId && collectionTokenId !== requiredCollectionTokenId) {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
identities.push({
|
|
287
|
+
tokenId: tokenId.toString(),
|
|
288
|
+
agentType,
|
|
289
|
+
collectionTokenId,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
identities.sort((a, b) => {
|
|
293
|
+
const tokenA = BigInt(a.tokenId);
|
|
294
|
+
const tokenB = BigInt(b.tokenId);
|
|
295
|
+
if (tokenA < tokenB)
|
|
296
|
+
return -1;
|
|
297
|
+
if (tokenA > tokenB)
|
|
298
|
+
return 1;
|
|
299
|
+
return 0;
|
|
300
|
+
});
|
|
301
|
+
return { identities, registryAddress, rpcUrl };
|
|
302
|
+
}
|
|
303
|
+
async function autoSelectAgentType(profile, wallet, nonInteractive) {
|
|
304
|
+
if (profile === 'local') {
|
|
305
|
+
return {};
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
const { identities } = await detectAgentIdentitiesFromChain(profile, wallet);
|
|
309
|
+
if (identities.length === 0) {
|
|
310
|
+
return {};
|
|
311
|
+
}
|
|
312
|
+
if (identities.length === 1 || nonInteractive) {
|
|
313
|
+
return { agentType: identities[0].agentType, identity: identities[0] };
|
|
314
|
+
}
|
|
315
|
+
const { selectedTokenId } = await inquirer.prompt([
|
|
316
|
+
{
|
|
317
|
+
type: 'list',
|
|
318
|
+
name: 'selectedTokenId',
|
|
319
|
+
message: 'Multiple agent identities detected. Select one:',
|
|
320
|
+
choices: identities.map((item) => ({
|
|
321
|
+
name: `${item.agentType} ${chalk.gray(`(tokenId=${item.tokenId}${item.collectionTokenId ? `, collection=${item.collectionTokenId}` : ''})`)}`,
|
|
322
|
+
value: item.tokenId,
|
|
323
|
+
})),
|
|
324
|
+
loop: false,
|
|
325
|
+
},
|
|
326
|
+
]);
|
|
327
|
+
const selected = identities.find((item) => item.tokenId === selectedTokenId) || identities[0];
|
|
328
|
+
return { agentType: selected.agentType, identity: selected };
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
console.log(chalk.yellow(`⚠ Could not auto-detect agent identity: ${error.message}`));
|
|
332
|
+
return {};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
186
335
|
async function promptForAgentType(initialValue, nonInteractive) {
|
|
187
336
|
if (initialValue) {
|
|
188
337
|
if (!AGENTS.some((agent) => agent.id === initialValue)) {
|
|
@@ -480,11 +629,25 @@ export async function initCommand(rawOptions = {}) {
|
|
|
480
629
|
console.log(chalk.gray('○ Ollama not detected'));
|
|
481
630
|
}
|
|
482
631
|
console.log();
|
|
483
|
-
const
|
|
484
|
-
const
|
|
632
|
+
const profile = await selectNetworkProfile(options.profile, nonInteractive);
|
|
633
|
+
const derivedWallet = getWalletFromRuntimePrivateKey();
|
|
634
|
+
const wallet = await promptForWallet(options.wallet || derivedWallet, nonInteractive);
|
|
635
|
+
if (!options.wallet && derivedWallet) {
|
|
636
|
+
console.log(chalk.green(`✓ Wallet auto-detected from TERMINUS_WALLET_PRIVATE_KEY: ${wallet}`));
|
|
637
|
+
}
|
|
638
|
+
let detectedIdentity;
|
|
639
|
+
let selectedAgentType = options.agentType;
|
|
640
|
+
if (!selectedAgentType) {
|
|
641
|
+
const detection = await autoSelectAgentType(profile, wallet, nonInteractive);
|
|
642
|
+
if (detection.agentType) {
|
|
643
|
+
selectedAgentType = detection.agentType;
|
|
644
|
+
detectedIdentity = detection.identity;
|
|
645
|
+
console.log(chalk.green(`✓ Agent type auto-detected: ${selectedAgentType}${detectedIdentity ? ` (tokenId=${detectedIdentity.tokenId})` : ''}`));
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
const agentType = await promptForAgentType(selectedAgentType, nonInteractive);
|
|
485
649
|
const llmProvider = await promptForProvider(options.llmProvider, nonInteractive);
|
|
486
650
|
const providerConfig = await configureProvider(llmProvider, options, nonInteractive, ollamaModels);
|
|
487
|
-
const profile = await selectNetworkProfile(options.profile, nonInteractive);
|
|
488
651
|
const controlPlaneUrl = await selectControlPlaneUrl(profile, options.controlPlaneUrl, nonInteractive);
|
|
489
652
|
const nodeId = generateNodeId(agentType, wallet);
|
|
490
653
|
const config = {
|
|
@@ -502,6 +665,12 @@ export async function initCommand(rawOptions = {}) {
|
|
|
502
665
|
const selectedAgent = AGENTS.find((item) => item.id === agentType);
|
|
503
666
|
console.log(chalk.green.bold('\nSetup complete.\n'));
|
|
504
667
|
printInfo('Agent', `${AGENT_EMOJIS[agentType] || '🤖'} ${selectedAgent?.name || agentType}`);
|
|
668
|
+
if (detectedIdentity) {
|
|
669
|
+
printInfo('Identity Token', detectedIdentity.tokenId);
|
|
670
|
+
if (detectedIdentity.collectionTokenId) {
|
|
671
|
+
printInfo('Collection Token', detectedIdentity.collectionTokenId);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
505
674
|
printInfo('Node ID', nodeId);
|
|
506
675
|
printInfo('Wallet', `${wallet.slice(0, 10)}...${wallet.slice(-8)}`);
|
|
507
676
|
printInfo('Provider', llmProvider);
|
package/dist/cli/run.js
CHANGED
|
@@ -80,10 +80,14 @@ export async function runCommand() {
|
|
|
80
80
|
console.log(chalk.cyan(' npx terminus-agent init --profile mainnet\n'));
|
|
81
81
|
process.exit(1);
|
|
82
82
|
}
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
console.log(chalk.
|
|
83
|
+
const runtimePrivateKey = process.env.TERMINUS_WALLET_PRIVATE_KEY?.trim();
|
|
84
|
+
if ((config.networkProfile === 'testnet' || config.networkProfile === 'mainnet') && !runtimePrivateKey) {
|
|
85
|
+
console.log(chalk.red('\n ❌ TERMINUS_WALLET_PRIVATE_KEY is required for testnet/mainnet challenge-signature auth.\n'));
|
|
86
|
+
console.log(chalk.gray(' Set it and retry:'));
|
|
87
|
+
console.log(chalk.cyan(' export TERMINUS_WALLET_PRIVATE_KEY=0x...\n'));
|
|
88
|
+
process.exit(1);
|
|
86
89
|
}
|
|
90
|
+
printStartupBanner(config);
|
|
87
91
|
const client = new AgentClient(config);
|
|
88
92
|
// Handle graceful shutdown
|
|
89
93
|
process.on('SIGINT', () => {
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ async function withCliErrorHandling(task) {
|
|
|
19
19
|
program
|
|
20
20
|
.name('terminus-agent')
|
|
21
21
|
.description('Standalone agent runner for Terminus network')
|
|
22
|
-
.version('0.1.
|
|
22
|
+
.version('0.1.3');
|
|
23
23
|
program
|
|
24
24
|
.command('init')
|
|
25
25
|
.description('Initialize agent configuration')
|