@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 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 agentType = await promptForAgentType(options.agentType, nonInteractive);
484
- const wallet = await promptForWallet(options.wallet, nonInteractive);
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
- printStartupBanner(config);
84
- if (!process.env.TERMINUS_WALLET_PRIVATE_KEY) {
85
- console.log(chalk.yellow(' TERMINUS_WALLET_PRIVATE_KEY is not set. Challenge-signature auth will fail in strict remote mode.\n'));
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.0');
22
+ .version('0.1.3');
23
23
  program
24
24
  .command('init')
25
25
  .description('Initialize agent configuration')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terminusagents/agents",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Standalone agent runner for Terminus network",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",